// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 import { ColoredTextStyle } from "./internal-components.slint"; export struct TimeSelectorStyle { foreground: brush, foreground-selected: brush, font-size: length, font-weight: float } component TimeSelector inherits Rectangle { in property selected; in property value; in property style; callback clicked <=> touch-area.clicked; width: max(48px, text-label.min-width); height: max(48px, text-label.min-height); border-radius: max(root.width, root.height) / 2; vertical-stretch: 0; horizontal-stretch: 0; touch-area := TouchArea { } text-label := Text { text: root.value; vertical-alignment: center; horizontal-alignment: center; color: root.style.foreground; font-size: root.style.font-size; font-weight: root.style.font-weight; } states [ selected when root.selected: { text-label.color: root.style.foreground-selected; } ] } export struct ClockStyle { background: brush, foreground: brush, time-selector-style: TimeSelectorStyle } export component Clock { in property <[int]> model; in property two-columns; in property style; in property total; in-out property current-item; in property current-value; callback current-item-changed(index: int); property radius: max(root.width, root.height) / 2; property picker-ditameter: 48px; 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: 256px; min-height: 256px; vertical-stretch: 0; horizontal-stretch: 0; background-layer := Rectangle { border-radius: max(self.width, self.height) / 2; background: root.style.background; } if root.current-item >= 0 || root.current-item < root.model.length: Path { stroke: root.style.foreground; 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: 8px; height: 8px; background: root.style.foreground; border-radius: 4px; } if root.current-item < 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: root.style.foreground; if root.current-item < 0: Rectangle { width: 4px; height: 4px; border-radius: 2px; background: root.style.time-selector-style.foreground; } } 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-item; style: root.style.time-selector-style; accessible-role: button; accessible-label: @tr("{} Hours or minutes of {}", val, root.total); accessible-action-default => { self.clicked(); } clicked => { root.set-current-item(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-item(index: int) { root.current-item-changed(index); } } export struct TimePickerInputStyle { background: brush, background-selected: brush, foreground: brush, foreground-selected: brush, border-radius: length, font-size: length, font-weight: float } export component TimePickerInput { in property style; in property read-only <=> text-input.read-only; in property checked; in-out property text <=> text-input.text; callback clicked; callback edited(int); min-width: max(96px, text-input.min-width); min-height: max(80px, text-input.min-height); vertical-stretch: 0; horizontal-stretch: 0; forward-focus: text-input; background-layer := Rectangle { border-radius: root.style.border-radius; background: root.style.background; } text-input := TextInput { vertical-alignment: center; horizontal-alignment: center; width: 100%; height: 100%; color: root.style.foreground; font-size: root.style.font-size; font-weight: root.style.font-weight; input-type: number; edited => { root.edited(self.text.to-float()); } } if root.read-only: TouchArea { clicked => { root.clicked(); } } states [ checked when root.checked: { background-layer.background: root.style.background-selected; text-input.color: root.style.foreground-selected; } ] } export struct PeriodSelectorItemStyle { font-size: length, font-weight: float, foreground: brush, background-selected: brush, foreground-selected: brush } export component PeriodSelectorItem { in property style; in property text <=> label.text; in property checked; callback clicked <=> touch-area.clicked; touch-area := TouchArea { } background-layer := Rectangle { } label := Text { font-size: root.style.font-size; font-weight: root.style.font-weight; color: root.style.foreground; horizontal-alignment: center; } states [ checked when root.checked: { background-layer.background: root.style.background-selected; label.color: root.style.foreground-selected; } ] } export struct PeriodSelectorStyle { item-style: PeriodSelectorItemStyle, border-brush: brush, border-radius: length, border-width: length} export component PeriodSelector { in property style; in property am-selected; callback update-period(bool); min-width: max(38px, layout.min-width); accessible-label: "AM or PM"; accessible-role: checkbox; accessible-checked: root.am-selected; Rectangle { border-radius: border.border-radius; clip: true; layout := VerticalLayout { PeriodSelectorItem { text: "AM"; checked: root.am-selected; style: root.style.item-style; 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; style: root.style.item-style; clicked => { if !root.am-selected { return; } root.update-period(false); } } } } border := Rectangle { border-radius: root.style.border-radius; border-width: root.style.border-width; border-color: root.style.border-brush; } } export struct Time { hour: int, minute: int, second: int } export struct TimePickerStyle { foreground: brush, horizontal-spacing: length, vertical-spacing: length, clock-style: ClockStyle, input-style: TimePickerInputStyle, period-selector-style: PeriodSelectorStyle, title-style: ColoredTextStyle, } export component TimePickerBase { in property use-24-hour-format: SlintInternal.use-24-hour-format; in property selection-mode: true; in property style; in property