// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: MIT import { BaseDialog } from "./dialog.slint"; import { Icons } from "../icons/icons.slint"; import { MaterialText } from "./material_text.slint"; import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint"; import { MaterialTypography } from "../styling/material_typography.slint"; import { MaterialPalette } from "../styling/material_palette.slint"; import { StateLayerArea } from "state_layer.slint"; component TimeSelector inherits Rectangle { in property selected; in property value; callback clicked <=> touch_area.clicked; width: max(MaterialStyleMetrics.size_48, text_label.min_width); height: max(MaterialStyleMetrics.size_48, text_label.min_height); border_radius: max(root.width, root.height) / 2; vertical_stretch: 0; horizontal_stretch: 0; touch_area := TouchArea { text_label := MaterialText { text: root.value; vertical_alignment: center; horizontal_alignment: center; style: MaterialTypography.body_large; color: MaterialPalette.on_surface; } } states [ selected when root.selected: { text_label.color: MaterialPalette.on_primary; } ] } component Clock { in property <[int]> model; in property two_columns; in property total; in_out property current_index; in property current_value; callback current_index_changed(index: int); property radius: max(root.width, root.height) / 2; property picker_ditameter: MaterialStyleMetrics.size_48; property center: root.radius - root.picker_ditameter / 2; property outer_padding: 2px; property inner_padding: 32px; property radius_outer: root.center - root.outer_padding; property radius_inner: root.center - root.inner_padding; property half_total: root.total / 2; property rotation: 0.25turn; property current_x: get_index_x(root.current_value); property current_y: get_index_y(root.current_value); min_width: MaterialStyleMetrics.size_256; min_height: self.min_width; vertical_stretch: 0; horizontal_stretch: 0; background_layer := Rectangle { border_radius: max(self.width, self.height) / 2; background: MaterialPalette.surface_container_highest; if root.current_index >= 0 || root.current_index < root.model.length: Path { stroke: MaterialPalette.primary; stroke_width: 2px; viewbox_width: self.width / 1px; viewbox_height: self.height / 1px; MoveTo { x: root.width / 2px; y: root.height / 2px; } LineTo { x: (root.current_x + root.picker_ditameter / 2) / 1px; y: (root.current_y + root.picker_ditameter / 2) / 1px; } } Rectangle { width: MaterialStyleMetrics.size_8; height: self.width; background: MaterialPalette.primary; border_radius: self.width / 2; } if root.current_index < root.model.length: Rectangle { x: root.current_x; y: root.current_y; width: root.picker_ditameter; height: root.picker_ditameter; border_radius: root.picker_ditameter / 2; background: MaterialPalette.primary; if root.current_index < 0: Rectangle { width: MaterialStyleMetrics.size_4; height: self.width; border_radius: self.width / 2; background: MaterialPalette.on_primary; } } for val[index] in root.model: TimeSelector { x: get_index_x(val); y: get_index_y(val); width: root.picker_ditameter; height: root.picker_ditameter; value: val; selected: index == root.current_index; accessible_role: button; accessible_label: @tr("{} Hours or minutes of {}", val, root.total); accessible_action_default => { self.clicked(); } clicked => { root.set_current_index(index); } } } pure function value_to_angle(value: int) -> angle { if root.two_columns { if value >= root.half_total { return clamp((value - root.half_total) / root.half_total * 1turn, 0, 0.999999turn) - root.rotation; } return clamp(value / root.half_total * 1turn, 0, 0.99999turn) - root.rotation; } clamp(value / root.total * 1turn, 0, 0.99999turn) - root.rotation } pure function get_index_x(value: int) -> length { if root.two_columns && value >= root.half_total { return root.center + (root.radius_inner / 1px * cos(root.value_to_angle(value))) * 1px; } root.center + (root.radius_outer / 1px * cos(root.value_to_angle(value))) * 1px } pure function get_index_y(value: int) -> length { // this is only for 24 mode if root.total == 24 && value == 0 { return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px; } if root.total == 24 && value == 12 { return root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px; } if root.two_columns && value >= root.half_total { return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px; } root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px } function set_current_index(index: int) { root.current_index_changed(index); } } component TimePickerInput { in property read_only <=> text_input.read_only; in property checked; in_out property text <=> text_input.text; callback clicked; callback edited(value: int); width: MaterialStyleMetrics.size_90; min_height: max(MaterialStyleMetrics.size_80, layout.min_height); vertical_stretch: 0; horizontal_stretch: 0; forward_focus: text_input; background_layer := Rectangle { border_radius: MaterialStyleMetrics.border_radius_8; background: MaterialPalette.surface_container_highest; clip: true; layout := HorizontalLayout { padding: MaterialStyleMetrics.padding_8; text_input := TextInput { vertical_alignment: center; horizontal_alignment: center; color: MaterialPalette.on_surface; font_size: MaterialTypography.display_large.font_size; font_weight: MaterialTypography.display_large.font_weight; input_type: number; visible: !root.read_only; edited => { root.edited(self.text.to_float()); } } } if root.read_only : StateLayerArea { border_radius: background_layer.border_radius; color: MaterialPalette.on_surface; HorizontalLayout { padding: MaterialStyleMetrics.padding_8; MaterialText { text: root.text.to_float() >= 10 ? root.text : "0" + root.text; style: MaterialTypography.display_large; color: MaterialPalette.on_surface; horizontal_alignment: center; vertical_alignment: center; } } clicked => { root.clicked(); } } } states [ has_focus when text_input.has_focus && !root.read_only: { background_layer.background: MaterialPalette.primary_container; background_layer.border_width: 2px; background_layer.border_color: MaterialPalette.primary; text_input.color: MaterialPalette.on_primary_container; } checked when root.checked: { background_layer.background: MaterialPalette.primary_container; text_input.color: MaterialPalette.on_primary_container; } ] } component PeriodSelectorItem { in property text <=> label.text; in property checked; callback clicked <=> state_layer.clicked; background_layer := Rectangle { state_layer := StateLayerArea { color: label.color; label := MaterialText { color: MaterialPalette.on_surface_variant; horizontal_alignment: center; } } } states [ checked when root.checked: { background_layer.background: MaterialPalette.tertiary_container; label.color: MaterialPalette.on_tertiary_container; } ] } component PeriodSelector { in property am_selected; in property horizontal; callback update_period(am_selected: bool); min_width: max(MaterialStyleMetrics.size_52, layout.min_width); min_height: max(root.horizontal ? MaterialStyleMetrics.size_38 : MaterialStyleMetrics.size_80, layout.min_height); accessible_label: "AM or PM"; accessible_role: checkbox; accessible_checked: root.am_selected; Rectangle { border_radius: border.border_radius; clip: true; layout := VerticalLayout { if root.horizontal : HorizontalLayout { PeriodSelectorItem { text: "AM"; checked: root.am_selected; clicked => { if root.am_selected { return; } root.update_period(true); } } Rectangle { width: 1px; background: border.border_color; vertical_stretch: 0; } PeriodSelectorItem { text: "PM"; checked: !root.am_selected; clicked => { if !root.am_selected { return; } root.update_period(false); } } } if !root.horizontal : VerticalLayout { PeriodSelectorItem { text: "AM"; checked: root.am_selected; clicked => { if root.am_selected { return; } root.update_period(true); } } Rectangle { height: 1px; background: border.border_color; vertical_stretch: 0; } PeriodSelectorItem { text: "PM"; checked: !root.am_selected; clicked => { if !root.am_selected { return; } root.update_period(false); } } } } } border := Rectangle { border_radius: MaterialStyleMetrics.border_radius_8; border_width: 1px; border_color: MaterialPalette.outline; } } export struct Time { hour: int, minute: int, second: int } export component TimePickerPopup inherits PopupWindow { in property use_24_hour_format: true; in property title: @tr("Select time"); in property cancel_text: @tr("Cancel"); in property ok_text: @tr("Ok"); in property