import { ScrollView, Button, TabWidget } 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 { changed width => { self.default-font-size = Data.get-default-font-size(); login_manager_underlay.width = max(self.width * 0.33, 640px); login_manager_underlay.height = self.height; } changed height => { self.default-font-size = Data.get-default-font-size(); login_manager_underlay.width = max(self.width * 0.33, 640px); login_manager_underlay.height = self.height; } default-font-size: Data.get-default-font-size(); in-out property selected_index <=> Data.selected_index; in-out property error_count <=> Data.error_count; in-out property error_content <=> error_popup.content; in property <[LoginOptionTileData]> tiles; in property default_username; out property 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(); background_timer.restart(); } full-screen: true; preferred-width: 1920px; preferred-height: 1080px; 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; 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; } } } 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; 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: 33%; private property 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 { clicked => { show_extended = !show_extended } } } login_manager_underlay := Rectangle { background: rgba(0, 0, 0, 0.5); drop-shadow-blur: 4rem; height: 0px; width: 0px; animate width { duration: 500ms; easing: ease-in-sine; } z: -1; VerticalLayout { clock := VerticalLayout { alignment: center; height: 15%; HorizontalLayout { 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: 70%; 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: 25%; 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 { 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: (self.viewport-width - self.height) / 2 - (Data.selected_index * (self.height)); scroll_layout := HorizontalLayout { height: parent.height; for tile[i] in tiles: LoginOptionTile { width: parent.height; icon: tile.image; label: tile.name; index: i; has_focus: environment_selector.has-focus; on_click => { Data.selected_index = self.index; environment_selector.focus() } } } } } // Username field HorizontalLayout { height: 2.5rem; alignment: center; username_box := LineEdit { font-size: 2rem; font-family: "monospace"; width: 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 { height: 2.5rem; alignment: center; password_box := LineEdit { font-size: 2rem; font-family: "monospace"; width: username_box.width; 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 { height: 2.5rem; alignment: center; IconButton { width: username_box.width; 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: 15%; 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 { 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 lastaction; padding-top: 0.4rem; 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: 25%; height: 12.5%; y: (parent.height * 0.025); opacity: 0.0; animate opacity { duration: 250ms; } in-out property content; Text { color: rgba(240, 240, 240, 1); width: 85%; height: 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; } } } }