mirror of
https://github.com/slint-ui/slint.git
synced 2025-07-16 01:25:27 +00:00

The ones that have a comment already. Tests that the name is consistent accross the styles.
508 lines
17 KiB
Text
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;
|
|
}
|
|
}
|