slint/internal/compiler/widgets/common/datepicker_base.slint
Olivier Goffart 929e71e6b0 Widgets: add callback arg names to some callback
The ones that have a comment already.

Tests that the name is consistent accross the styles.
2024-11-04 17:09:57 +01:00

508 lines
17 KiB
Text

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import { ScrollView, LineEdit } from "std-widgets-impl.slint";
import { StateLayer, IconButton, IconButtonStyle, SelectionButton, SelectionButtonStyle, ColoredTextStyle } from "./internal-components.slint";
export struct Date {
year: int,
month: int,
day: int,
}
export struct CalendarDelegateStyle {
font-size: length,
font-weight: float,
foreground: brush,
state-brush: brush,
background-selected: brush,
foreground-selected: brush,
state-brush-selected: brush,
border-color-today: brush,
foreground-today: brush,
state-brush-today: brush,
}
component CalendarHeaderDelegate {
in property <string> text <=> text-label.text;
in property <length> font-size;
in property <float> font-weight;
in property <brush> foreground;
min-width: max(40px, content-layer.min-width);
min-height: max(40px, content-layer.min-height);
content-layer := VerticalLayout {
text-label := Text {
vertical-alignment: center;
horizontal-alignment: center;
font-size: root.font-size;
font-weight: root.font-weight;
color: root.foreground;
}
}
}
component CalendarDelegate {
in property <bool> selected;
in property <bool> today;
in property <string> text <=> text-label.text;
in property <CalendarDelegateStyle> style;
in property <bool> enabled: true;
callback clicked <=> touch-area.clicked;
min-width: max(40px, content-layer.min-width);
min-height: max(40px, content-layer.min-height);
forward-focus: focus-scope;
accessible-role: button;
accessible-checkable: true;
accessible-checked: root.selected;
accessible-label: root.text;
accessible-action-default => { touch-area.clicked(); }
touch-area := TouchArea {
enabled: root.enabled;
}
focus-scope := FocusScope {
width: 0px;
enabled: root.enabled;
key-pressed(event) => {
if (event.text == " " || event.text == "\n") {
touch-area.clicked();
return accept;
}
return reject;
}
}
background-layer := Rectangle {
border-radius: self.height / 2;
}
content-layer := HorizontalLayout {
text-label := Text {
vertical-alignment: center;
horizontal-alignment: center;
color: root.style.foreground;
font-size: root.style.font-size;
font-weight: root.style.font-weight;
}
}
state-layer := StateLayer {
enabled: root.enabled;
border-radius: background-layer.border-radius;
pressed: touch-area.pressed;
has-hover: touch-area.has-hover;
has-focus: focus-scope.has-focus;
state-brush: root.style.state-brush;
}
states [
disabled when !root.enabled : {
root.opacity: 0.38;
}
selected when root.selected : {
background-layer.background: root.style.background-selected;
text-label.color: root.style.foreground-selected;
state-layer.state-brush: root.style.state-brush-selected;
}
today when root.today : {
background-layer.border-color: root.style.border-color-today;
background-layer.border-width: 1px;
state-layer.state-brush: root.style.state-brush-today;
}
]
}
export struct CalendarStyle {
delegate-style: CalendarDelegateStyle,
spacing: length,
}
export component Calendar {
in property <int> column-count;
in property <int> row-count;
in property <length> delegate-size;
in property <CalendarStyle> style;
in property <int> start-column;
in property <[string]> header-model;
in property <int> month-count;
in property <Date> today;
in property <Date> selected-date;
in property <int> display-month;
in property <int> display-year;
callback select-date(date: Date);
// header
for day[index] in root.header-model : CalendarHeaderDelegate {
x: root.delegate-x(index);
y: root.delegate-y(index);
text: day;
font-size: root.style.delegate-style.font-size;
font-weight: root.style.delegate-style.font-weight;
foreground: root.style.delegate-style.foreground;
}
// items
for index in root.month-count : CalendarDelegate {
property <Date> d: { day: index + 1, month: root.display-month, year: root.display-year };
x: root.delegate-x(root.index-on-calendar(index));
y: root.delegate-y(root.index-on-calendar(index));
width: root.delegate-size;
height: root.delegate-size;
text: index + 1;
style: root.style.delegate-style;
selected: root.selected-date == self.d;
today: root.today == self.d;
clicked => {
root.select-date(self.d);
}
}
function index-on-calendar(index: int) -> int {
// add column count because items starts after header row
root.column-count + root.start-column + index
}
function row-for-index(index: int) -> int {
floor(index / root.column-count)
}
function column-for-index(index: int) -> int {
mod(index, root.column-count)
}
function delegate-x(index: int) -> length {
root.column-for-index(index) * root.delegate-size + root.column-for-index(index) * root.style.spacing
}
function delegate-y(index: int) -> length {
root.row-for-index(index) * root.delegate-size + root.row-for-index(index) * root.style.spacing
}
}
component YearSelection {
in property <[int]> model;
in property <length> spacing;
in property <int> visible-row-count;
in property <int> column-count;
in property <length> delegate-width;
in property <length> delegate-height;
in property <CalendarDelegateStyle> delegate-style;
in property <int> selected-year;
in property <int> today-year;
callback select-year(year: int);
property <length> row-height: root.height / root.visible-row-count;
property <int> row-count: root.model.length / root.column-count;
property <length> viewport-height: root.row-count * root.row-height;
property <length> start-x: root.width / (root.column-count + 1);
property <length> start-y: root.height / (root.visible-row-count + 1);
ScrollView {
width: 100%;
height: 100%;
viewport-width: root.width;
viewport-height: root.viewport-height;
for year[index] in root.model: CalendarDelegate {
x: root.delegate-center-x(index) - self.width / 2;
y: root.delegate-center-y(index) - self.height / 2;
width: root.delegate-width;
height: root.delegate-height;
text: year;
style: root.delegate-style;
selected: year == root.selected-year;
today: year == root.today-year;
clicked => {
root.select-year(year);
}
}
}
function delegate-center-x(index: int) -> length {
root.start-x * (root.column-for-index(index) + 1)
}
function delegate-center-y(index: int) -> length {
root.start-y * (root.row-for-index(index) + 1)
}
function row-for-index(index: int) -> int {
floor(index / root.column-count)
}
function column-for-index(index: int) -> int {
mod(index, root.column-count)
}
}
export struct DatePickerStyle {
border-brush: brush,
horizontal-spacing: length,
vertical-spacing: length,
calendar-style: CalendarStyle,
icon-button-style: IconButtonStyle,
selection-button-style: SelectionButtonStyle,
current-day-style: ColoredTextStyle,
title-style: ColoredTextStyle,
next-icon: image,
previous-icon: image,
drop-down-icon: image,
input-icon: image,
calendar-icon: image,
}
export component DatePickerBase {
in property <Date> date : { day: today[0], month: today[1], year: today[2] };
in property <DatePickerStyle> style;
in property <string> title;
in property <string> input-title: @tr("Enter date");
in property <string> input-placeholder-text: "mm/dd/yyyy";
in property <string> input-format: "%m/%d/%Y";
// this is used for the navigation between months
property <Date> display-date: root.date;
property <Date> current-date: root.date;
property <length> delegate-size: 40px;
property <length> year-delegate-width: 72px;
property <int> calendar-column-count: 7;
property <int> calendar-row-count: 6;
property <length> calendar-min-width: root.delegate-size * root.calendar-column-count + (root.calendar-column-count - 1) * root.style.calendar-style.spacing;
property <length> calendar-min-height: root.delegate-size *(root.calendar-row-count + 1) + (root.calendar-row-count - 1) * root.style.calendar-style.spacing;
property <int> year-selection-column-count: 3;
property <int> year-selection-row-count: 5;
property <bool> year-selection;
property <bool> selection-mode: true;
property <string> current-input;
property <int> calendar-month-count: SlintInternal.month_day_count(root.display-date.month, root.display-date.year);
property <[string]> calendar-header-model: [
@tr("One-letter abbrev for Sunday" => "S"),
@tr("One-letter abbrev for Monday" => "M"),
@tr("One-letter abbrev for Tuesday" => "T"),
@tr("One-letter abbrev for Wednesday" => "W"),
@tr("One-letter abbrev for Thursday" => "T"),
@tr("One-letter abbrev for Friday" => "F"),
@tr("One-letter abbrev for Saturday" => "S"),
];
property <[int]> today: SlintInternal.date_now();
property <int> start-column: SlintInternal.month_offset(root.display-date.month, root.display-date.year);
property <string> current-month: SlintInternal.format_date("%B %Y", root.display-date.day, root.display-date.month, root.display-date.year);
property <[int]> year-model: [2024, 2025, 2026, 2027, 2028, 2029, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043];
property <int> selected-year: root.display-date.year;
property <int> today-year: root.today[2];
property <string> current-day: SlintInternal.format_date("%a, %b %d", root.current-date.day, root.current-date.month, root.current-date.year);
property <[int]> input-formatted: SlintInternal.parse_date(root.current-input, root.input-format);
min-width: content-layer.min-width;
min-height: content-layer.min-height;
content-layer := VerticalLayout {
spacing: root.style.vertical-spacing;
Text {
text: root.title;
horizontal-alignment: left;
overflow: elide;
font-size: root.style.title-style.font-size;
font-weight: root.style.title-style.font-weight;
color: root.style.title-style.foreground;
}
header := HorizontalLayout {
spacing: root.style.horizontal-spacing;
Text {
text: root.selection-mode ? root.current-day : root.input-title;
horizontal-alignment: left;
vertical-alignment: center;
font-size: root.style.current-day-style.font-size;
font-weight: root.style.current-day-style.font-weight;
color: root.style.current-day-style.foreground;
}
IconButton {
icon: root.selection-mode ? root.style.input-icon : root.style.calendar-icon;
style: root.style.icon-button-style;
accessible-label: "Toggle selection mode";
clicked => {
root.toggle-selection-mode();
}
}
}
Rectangle {
height: 1px;
background: root.style.border-brush;
}
if root.selection-mode : HorizontalLayout {
spacing: root.style.horizontal-spacing;
VerticalLayout {
horizontal-stretch: 0;
alignment: center;
SelectionButton {
text: root.current-month;
style: root.style.selection-button-style;
icon: root.style.drop-down-icon;
checked <=> root.year-selection;
}
}
Rectangle {}
IconButton {
icon: root.style.previous-icon;
style: root.style.icon-button-style;
accessible-label: "Previous month";
clicked => {
root.show-previous();
}
}
IconButton {
icon: root.style.next-icon;
style: root.style.icon-button-style;
accessible-label: "Next month";
clicked => {
root.show-next();
}
}
}
if root.selection-mode : VerticalLayout {
spacing: root.style.vertical-spacing;
if !root.year-selection : Calendar {
min-width: root.calendar-min-width;
min-height: root.calendar-min-height;
column-count: root.calendar-column-count;
row-count: root.calendar-row-count;
delegate-size: root.delegate-size;
style: root.style.calendar-style;
header-model: root.calendar-header-model;
month-count: root.calendar-month-count;
today: { day: root.today[0], month: root.today[1], year: root.today[2] };
selected-date <=> root.current-date;
start-column: root.start-column;
display-month: root.display-date.month;
display-year: root.display-date.year;
select-date(date) => {
root.select-date(date);
}
}
if root.year-selection : YearSelection {
min-width: root.calendar-min-width;
min-height: root.calendar-min-height;
spacing: root.style.calendar-style.spacing;
column-count: root.year-selection-column-count;
visible-row-count: root.year-selection-row-count;
delegate-width: root.year-delegate-width;
delegate-height: root.delegate-size;
model: root.year-model;
delegate-style: root.style.calendar-style.delegate-style;
selected-year: root.selected-year;
today-year: root.today-year;
select-year(year) => {
root.select-year(year);
}
}
}
Rectangle {
height: 1px;
background: root.style.border-brush;
visible: root.year-selection;
}
if !root.selection-mode : LineEdit {
text <=> root.current-input;
placeholder-text: root.input-placeholder-text;
}
}
changed date => {
root.display-date = root.date;
root.current-date = root.date;
}
changed selection-mode => {
// check switch from input mode if input is valid
if root.selection-mode && root.current-input-valid() {
root.current-date = root.input-as-date();
root.display-date = root.current-date;
}
}
pure public function ok-enabled() -> bool {
root.selection-mode || root.current-input-valid()
}
public function get-current-date() -> Date {
if root.selection-mode {
return root.current-date;
}
root.input-as-date()
}
pure function current-input-valid() -> bool {
SlintInternal.valid_date(root.current-input, root.input-format)
}
pure function input-as-date() -> Date {
{ day: root.input-formatted[0], month: root.input-formatted[1], year: root.input-formatted[2] }
}
function select-date(date: Date) {
root.current-date = date;
}
function select-year(year: int) {
root.current-date = { day: 1, month: 1, year: year };
root.display-date = root.current-date;
root.year-selection = false;
}
function show-next() {
if root.display-date.month >= 12 {
root.display-date = { day: 1, month: 1, year: root.display-date.year + 1 };
return;
}
root.display-date = { day: 1, month: root.display-date.month + 1, year: root.display-date.year };
}
function show-previous() {
if root.display-date.month <= 1 {
root.display-date = { day: 1, month: 12, year: root.display-date.year - 1 };
return;
}
root.display-date = { day: 1, month: root.display-date.month - 1, year: root.display-date.year };
}
function toggle-selection-mode() {
root.selection-mode = !root.selection-mode;
}
}