mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-10-31 03:54:25 +00:00 
			
		
		
		
	 929e71e6b0
			
		
	
	
		929e71e6b0
		
	
	
	
	
		
			
			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;
 | |
|     }
 | |
| }
 |