CKGreeter/ui/window.slint
CanadianBaconBoi defe26acc3 Initial Commit
2026-05-08 20:32:58 +02:00

514 lines
20 KiB
Plaintext

import { ScrollView, Button } from "std-widgets.slint";
import { Data, LoginOptionTileData } from "data.slint";
import { LoginOptionTile } from "envtile.slint";
import { LineEdit } from "widgets/fluent/lineedit.slint";
import { IconButton } from "iconbutton.slint";
export { Data as GreeterData } from "data.slint";
export component GreeterDisplay inherits Window {
default-font-size: root.width < root.height ? root.width * 0.01 : root.height * 0.01;
in-out property <int> selected_index <=> Data.selected_index;
in-out property <int> error_count <=> Data.error_count;
in-out property <string> error_content <=> error_popup.content;
in property <[LoginOptionTileData]> tiles;
in property <string> default_username;
out property <bool> background-toggle;
public function set_error(message: string) {
error_popup.content = message;
error_count += 1;
error_popup.opacity = 1.0;
error_popup_timer.start();
error_popup_timer.restart();
}
public function switch_background() {
background-toggle = !background-toggle;
background_timer.start();
clear_inactive_wallpaper_timer.start();
background_timer.restart();
clear_inactive_wallpaper_timer.restart();
}
full-screen: true;
min-width: 640px;
min-height: 480px;
TouchArea {
clicked => {
confirm_prompt.visible = false;
}
backdrop := Rectangle { // Almost black backdrop, in case background can't load
background: rgba(20, 20, 40);
z: -3;
}
background := Image { // Image background
z: -2;
animate opacity {
duration: 1000ms;
easing: ease-in-out;
}
source: Data.wallpaper-image;
image-fit: ImageFit.cover;
width: parent.width;
height: parent.height;
opacity: background-toggle && self.source.width > 0 ? 1.0 : 0.0;
background_timer := Timer {
interval: Data.wallpaper-cooldown * 1s;
running: true;
triggered() => {
Data.get-new-wallpaper();
self.running = false;
}
}
clear_inactive_wallpaper_timer := Timer {
interval: 1200ms;
running: false;
triggered() => {
Data.clear-inactive-wallpaper();
self.running = false;
}
}
}
background_1 := Image { // Background (secondary), needed for smooth opacity transitions
z: -2;
animate opacity {
duration: 1000ms;
easing: ease-in-out;
}
source: Data.wallpaper-image-1;
image-fit: ImageFit.cover;
width: parent.width;
height: parent.height;
opacity: !background-toggle && self.source.width > 0 ? 1.0 : 0.0;
}
background_overlay := Rectangle { // Background overlay
background: rgba(0, 0, 0, 0.5);
opacity: background.opacity > 0 || background_1.opacity > 0 ? 1.0 : 0.0;
z: -1;
}
image_info := Rectangle {
opacity: 0.6;
x: 1rem;
y: root.height - 7.5rem;
height: 6.5rem;
width: parent.width * 0.33;
private property <bool> show_extended;
VerticalLayout {
alignment: end;
spacing: 1rem;
img_link_info := HorizontalLayout {
visible: show_extended;
spacing: 0.5rem;
Image {
source: @image-url("icons/link.svg");
height: 1.25rem;
width: 1.25rem;
}
Text {
text: Data.wallpaper-url;
font-size: 1.25rem;
font-family: "monospace";
color: rgba(240, 240, 240, 1);
}
}
img_text_info := HorizontalLayout {
visible: show_extended;
spacing: 0.5rem;
Image {
source: @image-url("icons/text.svg");
height: 1.25rem;
width: 1.25rem;
}
Text {
text: Data.wallpaper-alt;
font-size: 1.25rem;
font-family: "monospace";
color: rgba(240, 240, 240, 1);
}
}
img_author_info := HorizontalLayout {
visible: true;
spacing: 0.5rem;
Image {
source: @image-url("icons/person.svg");
height: 1.25rem;
width: 1.25rem;
}
Text {
text: Data.wallpaper-author;
font-size: 1.25rem;
font-family: "monospace";
color: Data.wallpaper-author == "Error" ? rgba(255, 0, 0, 1) : rgba(240, 240, 240, 1);
}
}
}
TouchArea {
height: parent.height;
width: parent.width;
clicked => {
show_extended = !show_extended
}
}
}
login_manager_underlay := Rectangle {
background: rgba(0, 0, 0, 0.5);
drop-shadow-blur: 4rem;
height: parent.height;
z: -1;
width: max(parent.width * 0.33, 640px);
VerticalLayout {
height: parent.height;
width: parent.width;
clock := VerticalLayout {
alignment: center;
height: parent.height * 0.15;
HorizontalLayout {
width: parent.width;
alignment: center;
Rectangle {
y: 10rem;
width: 32rem;
height: 12rem;
VerticalLayout {
padding-top: 1rem;
Rectangle {
Text {
x: time_text.x - 0.1rem; // Offset
y: time_text.y + 0.1rem; // Offset
text: Data.current-time;
font-size: 6.4rem;
horizontal-alignment: center;
color: rgba(0, 0, 0, 1);
}
time_text := Text {
text: Data.current-time;
font-size: 6.4rem;
horizontal-alignment: center;
}
}
Rectangle {
Text {
x: date_text.x - 0.1rem; // Offset
y: date_text.y + 0.1rem; // Offset
text: date_text.text;
font-size: date_text.font-size;
horizontal-alignment: date_text.horizontal-alignment;
color: rgba(0, 0, 0, 1);
}
date_text := Text {
text: Data.current-date;
font-size: 1.6rem;
horizontal-alignment: center;
}
}
}
}
}
}
login_model := VerticalLayout {
alignment: center;
height: parent.height * 0.70;
width: parent.width;
spacing: 1rem;
init_timer := Timer {
interval: 0s;
running: true;
triggered => {
if (!Data.has-default-username) {
username_box.focus();
} else {
password_box.focus();
}
self.running = false;
}
}
environment_selector := FocusScope {
height: max(parent.width * 0.2, parent.height * 0.2);
width: parent.width;
key-pressed(event) => {
if (event.text == Key.LeftArrow || event.text == "a" || event.text == "A") {
if (Data.selected_index == 0) {
Data.selected_index = tiles.length - 1;
} else {
Data.selected_index -= 1;
}
accept
} else if (event.text == Key.RightArrow || event.text == "d" || event.text == "D") {
if (Data.selected_index + 1 == tiles.length) {
Data.selected_index = 0;
} else {
Data.selected_index += 1;
}
accept
}
reject
}
scroll_view := ScrollView {
height: parent.height;
width: min(((self.height + 1rem) * tiles.length) - 1rem, parent.width);
animate viewport-x { duration: 250ms; }
viewport-width: ((self.height + 1rem) * tiles.length) - 1rem;
// viewport-x: -(Data.selected_index * self.height) + (self.height / 2);
viewport-x: (self.viewport-width - self.height) / 2 - (Data.selected_index * (self.height));
//x: self.viewport-width < self.width ? ((self.width - self.viewport-width) / 2) : 0;
scroll_layout := HorizontalLayout {
height: parent.height;
for tile[i] in tiles: LoginOptionTile {
width: parent.height;
height: parent.height;
icon: tile.image;
label: tile.name;
index: i;
has_focus: environment_selector.has-focus;
on_click => {
Data.selected_index = self.index;
}
}
}
}
}
// Username field
HorizontalLayout {
width: parent.width;
height: 2.5rem;
alignment: center;
username_box := LineEdit {
font-size: 2rem;
font-family: "monospace";
width: parent.width * 0.45;
placeholder-text: "Username";
text: default_username;
border-color: rgba(185, 15, 220, 0.85);
on-focus-change => {
Data.check-lock-states()
}
key-released => {
Data.check-lock-states();
return EventResult.accept;
}
accepted(text) => {
Data.login(username_box.text, password_box.text)
}
}
}
// Password field
HorizontalLayout {
width: parent.width;
height: 2.5rem;
alignment: center;
password_box := LineEdit {
font-size: 2rem;
font-family: "monospace";
width: parent.width * 0.45;
placeholder-text: "Password";
input-type: password;
border-color: rgba(185, 15, 220, 0.85);
on-focus-change => {
Data.check-lock-states()
}
key-released => {
Data.check-lock-states();
return EventResult.accept;
}
accepted(text) => {
Data.login(username_box.text, password_box.text)
}
}
}
// Submit button
HorizontalLayout {
width: parent.width;
height: 2.5rem;
alignment: center;
IconButton {
width: parent.width * 0.45; //min(parent.width * 0.85, 32rem);
icon: @image-url("icons/right_arrow.svg");
icon-width: 4rem;
icon-height: self.icon-width/3;
icon-align: LayoutAlignment.end;
text: "Login";
font-size: 1.5rem;
clicked => {
Data.login(username_box.text, password_box.text);
}
}
}
}
power_model := VerticalLayout {
height: parent.height * 0.15;
width: parent.width;
confirm_prompt := Text {
horizontal-alignment: center;
font-size: 1.5rem;
text: "Press again to confirm ";
visible: false;
vertical-alignment: bottom;
confirm_prompt_timer := Timer {
interval: 5s;
running: false;
triggered() => {
confirm_prompt.visible = false;
self.running = false;
}
}
}
HorizontalLayout {
width: parent.width;
alignment: center;
Rectangle {
background: rgba(0, 0, 0, 0.6);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
width: 16.3rem;
height: 5.8rem;
// Power action buttons
HorizontalLayout {
private property <string> lastaction;
padding-top: 0.4rem;
width: parent.width;
alignment: center;
spacing: 0.5rem;
function power_action(action: string, command: string) {
if (lastaction != action || confirm_prompt.visible == false) {
lastaction = action;
confirm_prompt.text = "Press again to confirm " + action;
confirm_prompt.visible = true;
confirm_prompt_timer.start();
confirm_prompt_timer.restart();
} else {
if (lastaction == action) {
Data.loginctl(command)
} else {
confirm_prompt.visible = false;
}
}
}
IconButton {
width: 4.8rem;
height: 4.8rem;
icon: @image-url("icons/power.svg");
icon-width: self.width * 0.5;
icon-height: self.width * 0.5;
clicked => {
power_action("poweroff", "poweroff")
}
}
IconButton {
width: 4.8rem;
height: 4.8rem;
icon: @image-url("icons/restart.svg");
icon-width: self.width * 0.5;
icon-height: self.width * 0.5;
clicked => {
power_action("restart", "reboot")
}
}
IconButton {
width: 4.8rem;
height: 4.8rem;
icon: @image-url("icons/sleep.svg");
icon-width: self.width * 0.5;
icon-height: self.width * 0.5;
clicked => {
power_action("sleep", "suspend")
}
}
}
}
}
}
}
}
error_popup := Rectangle {
background: rgba(220, 50, 50, 0.85);
width: min(parent.width * 0.5, 64rem);
height: max(parent.height * 0.125, 6rem);
y: (parent.height * 0.025);
opacity: 0.0;
animate opacity { duration: 250ms; }
in-out property <string> content;
Text {
color: rgba(240, 240, 240, 1);
width: parent.width * 0.85;
height: parent.height * 0.85;
vertical-alignment: center;
horizontal-alignment: center;
font-family: "monospace";
font-size: max(1rem, scroll_view.height * 0.125);
text: "[" + Data.error_count + "] " + content;
}
}
error_popup_timer := Timer {
interval: 5s;
running: false;
triggered() => {
error_popup.opacity = 0.0;
self.running = false;
}
}
}
}