ui: Mass layout refactor
- Replaced TabWidget mode selection with ComboBox - Moved Timeline out of record/review widgets - Added TimelineState struct - Set slint style to cosmic
This commit is contained in:
parent
365f77056e
commit
1a1f6dde83
7 changed files with 190 additions and 160 deletions
5
build.rs
5
build.rs
|
|
@ -1,3 +1,6 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
slint_build::compile("ui/app-window.slint").expect("Slint build failed");
|
let config = slint_build::CompilerConfiguration::new()
|
||||||
|
.with_style("cosmic".into());
|
||||||
|
|
||||||
|
slint_build::compile_with_config("ui/app-window.slint", config).expect("Slint build failed");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
38
src/main.rs
38
src/main.rs
|
|
@ -8,9 +8,6 @@ use chrono::{Datelike, Timelike};
|
||||||
use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
|
use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
|
||||||
use toml::value::{Date as TomlDate, Time};
|
use toml::value::{Date as TomlDate, Time};
|
||||||
|
|
||||||
const DEFAULT_WINDOW_WIDTH: f32 = 800.;
|
|
||||||
const DEFAULT_WINDOW_HEIGHT: f32 = 600.;
|
|
||||||
|
|
||||||
slint::include_modules!();
|
slint::include_modules!();
|
||||||
|
|
||||||
impl From<Event> for TimelineEvent {
|
impl From<Event> for TimelineEvent {
|
||||||
|
|
@ -60,7 +57,9 @@ fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>) {
|
||||||
.collect();
|
.collect();
|
||||||
let in_progress = events.iter().any(|event| !event.finished);
|
let in_progress = events.iter().any(|event| !event.finished);
|
||||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||||
ui.set_record_events(model);
|
let mut state = ui.get_record_state();
|
||||||
|
state.events = model;
|
||||||
|
ui.set_record_state(state);
|
||||||
ui.set_in_progress(in_progress);
|
ui.set_in_progress(in_progress);
|
||||||
ui.invoke_get_previous_event();
|
ui.invoke_get_previous_event();
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +108,6 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
load_colors(ui_weak, config_arc);
|
load_colors(ui_weak, config_arc);
|
||||||
|
|
||||||
ui.invoke_update_record_offset(offset as i32);
|
ui.invoke_update_record_offset(offset as i32);
|
||||||
ui.invoke_update_window_size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
|
|
||||||
|
|
||||||
ui.on_fetch_log({
|
ui.on_fetch_log({
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
|
|
@ -127,7 +125,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.map(|event| TimelineEvent::from((*event).clone()))
|
.map(|event| TimelineEvent::from((*event).clone()))
|
||||||
.collect();
|
.collect();
|
||||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||||
ui.set_review_events(model);
|
let mut state = ui.get_review_state();
|
||||||
|
state.events = model;
|
||||||
|
ui.set_review_state(state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -151,10 +151,14 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.map(|h| h.parse::<i32>().unwrap())
|
.map(|h| h.parse::<i32>().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if is_record {
|
if is_record {
|
||||||
ui.set_record_visible_time(hours * 3600);
|
let mut state = ui.get_record_state();
|
||||||
|
state.visible_time = hours * 3600;
|
||||||
|
ui.set_record_state(state);
|
||||||
} else {
|
} else {
|
||||||
ui.set_review_visible_time(hours * 3600);
|
let mut state = ui.get_review_state();
|
||||||
}
|
state.visible_time = hours * 3600;
|
||||||
|
ui.set_review_state(state);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -163,18 +167,17 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let log = writing_log.clone();
|
let log = writing_log.clone();
|
||||||
move |event_name: SharedString| {
|
move |event_name: SharedString| {
|
||||||
let ui = ui_weak.unwrap();
|
let ui = ui_weak.unwrap();
|
||||||
|
|
||||||
let events_rc = ui.get_record_events();
|
let state = ui.get_record_state();
|
||||||
let events = events_rc.as_any()
|
let events = state.events.as_any()
|
||||||
.downcast_ref::<VecModel<TimelineEvent>>()
|
.downcast_ref::<VecModel<TimelineEvent>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let offset = ui.get_record_offset();
|
|
||||||
|
|
||||||
let event = TimelineEvent {
|
let event = TimelineEvent {
|
||||||
duration: 0,
|
duration: 0,
|
||||||
finished: false,
|
finished: false,
|
||||||
label: event_name.clone(),
|
label: event_name.clone(),
|
||||||
start: offset,
|
start: state.offset,
|
||||||
color_id: color_id_from_name(event_name.to_string())
|
color_id: color_id_from_name(event_name.to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -194,11 +197,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let log = writing_log.clone();
|
let log = writing_log.clone();
|
||||||
move || {
|
move || {
|
||||||
let ui = ui_weak.unwrap();
|
let ui = ui_weak.unwrap();
|
||||||
let events_rc = ui.get_record_events();
|
let state = ui.get_record_state();
|
||||||
let events = events_rc.as_any()
|
let events = state.events.as_any()
|
||||||
.downcast_ref::<VecModel<TimelineEvent>>()
|
.downcast_ref::<VecModel<TimelineEvent>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let offset = ui.get_record_offset();
|
|
||||||
|
|
||||||
let event_id = events.iter()
|
let event_id = events.iter()
|
||||||
.position(|data| !data.finished)
|
.position(|data| !data.finished)
|
||||||
|
|
@ -206,7 +208,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let event = events.row_data(event_id)
|
let event = events.row_data(event_id)
|
||||||
.expect("stop-event called without unfinished events");
|
.expect("stop-event called without unfinished events");
|
||||||
let new_event = TimelineEvent {
|
let new_event = TimelineEvent {
|
||||||
duration: offset - event.start,
|
duration: state.offset - event.start,
|
||||||
finished: true,
|
finished: true,
|
||||||
label: event.label.clone(),
|
label: event.label.clone(),
|
||||||
start: event.start,
|
start: event.start,
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,104 @@
|
||||||
import { TabWidget } from "std-widgets.slint";
|
import { TabWidget, VerticalBox, ComboBox } from "std-widgets.slint";
|
||||||
import { RecordWidget } from "record.slint";
|
import { RecordWidget } from "record.slint";
|
||||||
import { ReviewWidget } from "review.slint";
|
import { ReviewWidget } from "review.slint";
|
||||||
import { TimelineEvent } from "timeline.slint";
|
import { TimelineEvent, Timeline, TimelineState } from "timeline.slint";
|
||||||
|
import { Const } from "global.slint";
|
||||||
export { Palette } from "theme.slint";
|
export { Palette } from "theme.slint";
|
||||||
|
|
||||||
export component AppWindow inherits Window {
|
export component AppWindow inherits Window {
|
||||||
|
callback update-record-offset(int);
|
||||||
|
callback save-log;
|
||||||
|
callback new-day-started();
|
||||||
|
|
||||||
|
update-record-offset(new-offset) => {
|
||||||
|
record-state.offset = new-offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
preferred-width: 800px;
|
||||||
|
preferred-height: 600px;
|
||||||
|
max-width: 2147483647px;
|
||||||
|
|
||||||
callback start-new-event <=> record.start-new-event;
|
callback start-new-event <=> record.start-new-event;
|
||||||
callback stop-event <=> record.stop-event;
|
callback stop-event <=> record.stop-event;
|
||||||
callback chain-event <=> record.chain-event;
|
callback chain-event <=> record.chain-event;
|
||||||
callback new-day-started <=> record.new-day-started;
|
|
||||||
callback get-previous-event <=> record.get-previous-event;
|
callback get-previous-event <=> record.get-previous-event;
|
||||||
callback update-record-offset(int);
|
|
||||||
callback update-window-size(length, length);
|
|
||||||
callback save-log;
|
|
||||||
|
|
||||||
callback fetch-log <=> review.fetch-log;
|
callback fetch-log <=> review.fetch-log;
|
||||||
|
|
||||||
callback update-visible-time(bool, string);
|
callback update-visible-time(bool, string);
|
||||||
|
|
||||||
update-record-offset(new-offset) => {
|
|
||||||
record.offset = new-offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
update-window-size(width, height) => {
|
|
||||||
self.width = width;
|
|
||||||
self.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
in-out property record-events <=> record.events;
|
|
||||||
in-out property record-offset <=> record.offset;
|
|
||||||
in-out property record-visible-time <=> record.visible-time;
|
|
||||||
in-out property in-progress <=> record.in-progress;
|
|
||||||
in property previous-event-name <=> record.previous-event-name;
|
|
||||||
|
|
||||||
in-out property review-events <=> review.events;
|
in-out property record-state <=> record.state;
|
||||||
in-out property review-offset <=> review.offset;
|
|
||||||
in-out property review-visible-time <=> review.visible-time;
|
in-out property review-state <=> review.state;
|
||||||
|
|
||||||
|
in-out property in-progress <=> record.in-progress;
|
||||||
|
|
||||||
|
in property previous-event-name <=> record.previous-event-name;
|
||||||
|
|
||||||
property<[string]> combo-spans: ["1 Hour", "4 Hours", "8 Hours", "24 Hours"];
|
property<[string]> combo-spans: ["1 Hour", "4 Hours", "8 Hours", "24 Hours"];
|
||||||
|
property<bool> minimized: false;
|
||||||
|
property<bool> in-record-mode: true;
|
||||||
|
|
||||||
|
init => {
|
||||||
|
record-state.visible-time = 3600;
|
||||||
|
review-state.visible-time = 3600;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 1s;
|
||||||
|
running: true;
|
||||||
|
triggered => {
|
||||||
|
if (record-state.offset >= Const.max-offset) {
|
||||||
|
root.new-day-started();
|
||||||
|
record-state.offset = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
record-state.offset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
title: "Aliveline";
|
title: "Aliveline";
|
||||||
TabWidget {
|
VerticalLayout {
|
||||||
Tab {
|
width: 100%;
|
||||||
title: "Record";
|
height: 100%;
|
||||||
record := RecordWidget {
|
tl := Timeline {
|
||||||
combo-spans: combo-spans;
|
preferred-height: 100%;
|
||||||
update-visible-time(time) => {
|
state: in-record-mode ? record-state : review-state;
|
||||||
root.update-visible-time(true, time);
|
clicked => {
|
||||||
}
|
minimized = !minimized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tab {
|
spacing: minimized ? 0 : 8px;
|
||||||
title: "Review";
|
record := RecordWidget {
|
||||||
review := ReviewWidget {
|
combo-spans: combo-spans;
|
||||||
combo-spans: combo-spans;
|
update-visible-time(time) => {
|
||||||
update-visible-time(time) => {
|
root.update-visible-time(true, time);
|
||||||
root.update-visible-time(false, time)
|
}
|
||||||
|
minimized: minimized || !in-record-mode;
|
||||||
|
}
|
||||||
|
review := ReviewWidget {
|
||||||
|
combo-spans: combo-spans;
|
||||||
|
update-visible-time(time) => {
|
||||||
|
root.update-visible-time(false, time)
|
||||||
|
}
|
||||||
|
minimized: minimized;
|
||||||
|
is-active: !in-record-mode;
|
||||||
|
}
|
||||||
|
if !minimized: HorizontalLayout {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
spacing: 16px;
|
||||||
|
Text {
|
||||||
|
text: "Mode:";
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: right;
|
||||||
|
}
|
||||||
|
ComboBox {
|
||||||
|
model: ["Record", "Review"];
|
||||||
|
current-index: in-record-mode ? 0 : 1;
|
||||||
|
selected(current-value) => {
|
||||||
|
in-record-mode = current-value == "Record";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
ui/global.slint
Normal file
21
ui/global.slint
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
export global TimeString {
|
||||||
|
pure function pad-mh(seconds: int, param: int) -> string {
|
||||||
|
if seconds / param < 10 {
|
||||||
|
return "0\{floor(seconds / param)}";
|
||||||
|
}
|
||||||
|
return "\{floor(seconds / param)}";
|
||||||
|
}
|
||||||
|
pure function pad-s(seconds: int) -> string {
|
||||||
|
if mod(seconds, 60) < 10 {
|
||||||
|
return "0\{mod(seconds, 60)}";
|
||||||
|
}
|
||||||
|
return "\{mod(seconds, 60)}";
|
||||||
|
}
|
||||||
|
public pure function from(seconds: int) -> string {
|
||||||
|
return "\{pad-mh(seconds, 3600)}:\{pad-mh(mod(seconds, 3600), 60)}:\{pad-s(seconds)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export global Const {
|
||||||
|
out property <int> max-offset: 24 * 3600 - 1;
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,25 @@
|
||||||
import { VerticalBox, LineEdit, Button, ComboBox } from "std-widgets.slint";
|
import { VerticalBox, LineEdit, Button, ComboBox } from "std-widgets.slint";
|
||||||
import { Timeline } from "timeline.slint";
|
import { Timeline, TimelineState } from "timeline.slint";
|
||||||
|
|
||||||
export component RecordWidget inherits VerticalBox {
|
export component RecordWidget inherits VerticalLayout {
|
||||||
callback new-day-started <=> tl.new-day-started;
|
|
||||||
callback update-visible-time(string);
|
callback update-visible-time(string);
|
||||||
callback start-new-event(string);
|
callback start-new-event(string);
|
||||||
callback chain-event(string);
|
callback chain-event(string);
|
||||||
callback stop-event;
|
callback stop-event;
|
||||||
callback get-previous-event();
|
callback get-previous-event();
|
||||||
in-out property visible-time <=> tl.visible-time;
|
|
||||||
in-out property updating <=> tl.updating;
|
in-out property<TimelineState> state;
|
||||||
in-out property offset <=> tl.offset;
|
|
||||||
in-out property events <=> tl.events;
|
|
||||||
in property<[string]> combo-spans: [];
|
in property<[string]> combo-spans: [];
|
||||||
in-out property<bool> in-progress: false;
|
in-out property<bool> in-progress: false;
|
||||||
property<string> event-name: "";
|
property<string> event-name: "";
|
||||||
in property<string> previous-event-name: "";
|
in property<string> previous-event-name: "";
|
||||||
property<bool> minimized: false;
|
|
||||||
property<int> combo-index: 0;
|
property<int> combo-index: 0;
|
||||||
tl := Timeline {
|
in-out property <bool> minimized;
|
||||||
preferred-height: 100%;
|
|
||||||
updating: true;
|
|
||||||
clicked => {
|
|
||||||
minimized = !minimized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !minimized: GridLayout {
|
if !minimized: GridLayout {
|
||||||
spacing-vertical: 8px;
|
spacing-vertical: 8px;
|
||||||
spacing-horizontal: 16px;
|
spacing-horizontal: 16px;
|
||||||
|
padding: 8px;
|
||||||
le := LineEdit {
|
le := LineEdit {
|
||||||
placeholder-text: "Event name";
|
placeholder-text: "Event name";
|
||||||
text: event-name;
|
text: event-name;
|
||||||
|
|
@ -56,7 +48,7 @@ export component RecordWidget inherits VerticalBox {
|
||||||
Button {
|
Button {
|
||||||
text: "Chain";
|
text: "Chain";
|
||||||
enabled: in-progress;
|
enabled: in-progress;
|
||||||
col: 3;
|
col: 2;
|
||||||
row: 1;
|
row: 1;
|
||||||
colspan: 2;
|
colspan: 2;
|
||||||
clicked => {
|
clicked => {
|
||||||
|
|
@ -67,7 +59,7 @@ export component RecordWidget inherits VerticalBox {
|
||||||
Button {
|
Button {
|
||||||
text: previous-event-name == "" ? "Chain previous event (None)" : "Chain previous event (\{previous-event-name})";
|
text: previous-event-name == "" ? "Chain previous event (None)" : "Chain previous event (\{previous-event-name})";
|
||||||
enabled: in-progress && previous-event-name != "";
|
enabled: in-progress && previous-event-name != "";
|
||||||
col: 5;
|
col: 4;
|
||||||
row: 1;
|
row: 1;
|
||||||
colspan: 2;
|
colspan: 2;
|
||||||
clicked => {
|
clicked => {
|
||||||
|
|
@ -77,7 +69,7 @@ export component RecordWidget inherits VerticalBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: "Span:";
|
text: "Span: ";
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
row: 2;
|
row: 2;
|
||||||
colspan: 3;
|
colspan: 3;
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,58 @@
|
||||||
import { VerticalBox, LineEdit, Button, DatePickerPopup, ComboBox, Slider } from "std-widgets.slint";
|
import { VerticalBox, LineEdit, Button, DatePickerPopup, ComboBox, Slider } from "std-widgets.slint";
|
||||||
import { Timeline } from "timeline.slint";
|
import { Timeline, TimelineState } from "timeline.slint";
|
||||||
|
import { Const } from "global.slint";
|
||||||
|
|
||||||
export component ReviewWidget inherits VerticalBox {
|
export component ReviewWidget inherits VerticalLayout {
|
||||||
callback update-visible-time(string);
|
callback update-visible-time(string);
|
||||||
callback fetch-log(int, int, int);
|
callback fetch-log(int, int, int);
|
||||||
|
|
||||||
property<int> max-offset: 24 * 3600;
|
|
||||||
property<int> current-year;
|
property<int> current-year;
|
||||||
property<int> current-month;
|
property<int> current-month;
|
||||||
property<int> current-day;
|
property<int> current-day;
|
||||||
in property<[string]> combo-spans: [];
|
in property<[string]> combo-spans: [];
|
||||||
in-out property visible-time <=> tl.visible-time;
|
in-out property<TimelineState> state;
|
||||||
in-out property offset <=> tl.offset;
|
in-out property<bool> minimized;
|
||||||
in-out property events <=> tl.events;
|
in-out property<bool> is-active;
|
||||||
|
|
||||||
tl := Timeline {
|
if is-active: VerticalLayout {
|
||||||
updating: false;
|
spacing: 8px;
|
||||||
}
|
|
||||||
GridLayout {
|
|
||||||
spacing-vertical: 8px;
|
|
||||||
spacing-horizontal: 16px;
|
|
||||||
Slider {
|
Slider {
|
||||||
minimum: visible-time;
|
minimum: state.visible-time;
|
||||||
maximum: tl.max-offset;
|
maximum: Const.max-offset;
|
||||||
value: offset;
|
value: state.offset;
|
||||||
row: 0;
|
|
||||||
colspan: 2;
|
|
||||||
changed(value) => {
|
changed(value) => {
|
||||||
offset = value;
|
state.offset = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text {
|
if !minimized: GridLayout {
|
||||||
text: "Day: \{current-day}/\{current-month}/\{current-year}";
|
spacing-horizontal: 16px;
|
||||||
font-size: 32px;
|
padding: 8px;
|
||||||
horizontal-alignment: right;
|
Text {
|
||||||
row: 1;
|
text: "Day: \{current-day}/\{current-month}/\{current-year}";
|
||||||
}
|
font-size: 24px;
|
||||||
Button {
|
horizontal-alignment: right;
|
||||||
text: "Select";
|
|
||||||
clicked => {
|
|
||||||
date-picker.show()
|
|
||||||
}
|
}
|
||||||
row: 1;
|
Button {
|
||||||
col: 1;
|
text: "Select";
|
||||||
}
|
clicked => {
|
||||||
Text {
|
date-picker.show()
|
||||||
text: "Span: ";
|
}
|
||||||
font-size: 24px;
|
col: 1;
|
||||||
row: 2;
|
}
|
||||||
horizontal-alignment: right;
|
Text {
|
||||||
}
|
text: "Span: ";
|
||||||
ComboBox {
|
font-size: 24px;
|
||||||
model: combo-spans;
|
row: 1;
|
||||||
current-index: 0;
|
horizontal-alignment: right;
|
||||||
row: 2;
|
}
|
||||||
col: 1;
|
ComboBox {
|
||||||
selected(current-value) => {
|
model: combo-spans;
|
||||||
root.update-visible-time(current-value);
|
current-index: 0;
|
||||||
|
row: 1;
|
||||||
|
col: 1;
|
||||||
|
selected(current-value) => {
|
||||||
|
root.update-visible-time(current-value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,28 @@
|
||||||
import { Palette } from "theme.slint";
|
import { Palette } from "theme.slint";
|
||||||
|
import { TimeString, Const } from "global.slint";
|
||||||
|
|
||||||
export struct TimelineEvent {
|
export struct TimelineEvent {
|
||||||
start: int,
|
start: int,
|
||||||
duration: int,
|
duration: int,
|
||||||
finished: bool,
|
finished: bool,
|
||||||
label: string,
|
label: string,
|
||||||
color-id: int
|
color-id: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
global TimeString {
|
export struct TimelineState {
|
||||||
pure function pad-mh(seconds: int, param: int) -> string {
|
visible-time: int,
|
||||||
if seconds / param < 10 {
|
offset: int,
|
||||||
return "0\{floor(seconds / param)}";
|
events: [TimelineEvent],
|
||||||
}
|
|
||||||
return "\{floor(seconds / param)}";
|
|
||||||
}
|
|
||||||
pure function pad-s(seconds: int) -> string {
|
|
||||||
if mod(seconds, 60) < 10 {
|
|
||||||
return "0\{mod(seconds, 60)}";
|
|
||||||
}
|
|
||||||
return "\{mod(seconds, 60)}";
|
|
||||||
}
|
|
||||||
public pure function from(seconds: int) -> string {
|
|
||||||
return "\{pad-mh(seconds, 3600)}:\{pad-mh(mod(seconds, 3600), 60)}:\{pad-s(seconds)}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export component Timeline inherits Rectangle {
|
export component Timeline inherits Rectangle {
|
||||||
callback new-day-started;
|
|
||||||
callback clicked <=> ta.clicked;
|
callback clicked <=> ta.clicked;
|
||||||
|
|
||||||
background: Palette.background;
|
background: Palette.background;
|
||||||
|
|
||||||
in-out property<bool> updating: true;
|
in-out property<TimelineState> state;
|
||||||
in-out property<[TimelineEvent]> events: [];
|
property<int> visible-offset: max(state.offset, state.visible-time);
|
||||||
in-out property<int> visible-time: 3600;
|
out property<int> max-offset: Const.max-offset;
|
||||||
property<int> visible-offset: max(offset, visible-time);
|
|
||||||
in-out property<int> offset: 0;
|
|
||||||
out property<int> max-offset: 24 * 3600 - 1;
|
|
||||||
|
|
||||||
timer := Timer {
|
|
||||||
interval: 1s;
|
|
||||||
running: updating;
|
|
||||||
triggered => {
|
|
||||||
if (offset >= max-offset) {
|
|
||||||
root.new-day-started();
|
|
||||||
offset = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ta := TouchArea {
|
ta := TouchArea {
|
||||||
preferred-width: 100%;
|
preferred-width: 100%;
|
||||||
|
|
@ -72,7 +45,7 @@ export component Timeline inherits Rectangle {
|
||||||
Text {
|
Text {
|
||||||
x: 0;
|
x: 0;
|
||||||
y: parent.height - self.height;
|
y: parent.height - self.height;
|
||||||
text: TimeString.from(visible-offset - visible-time);
|
text: TimeString.from(visible-offset - state.visible-time);
|
||||||
color: Palette.background-text;
|
color: Palette.background-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,9 +56,9 @@ export component Timeline inherits Rectangle {
|
||||||
color: Palette.background-text;
|
color: Palette.background-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
for event in events: timeline-event := Rectangle {
|
for event in state.events: timeline-event := Rectangle {
|
||||||
property<length> real-x: ((visible-time - (visible-offset - event.start)) / visible-time) * parent.width;
|
property<length> real-x: ((state.visible-time - (visible-offset - event.start)) / state.visible-time) * parent.width;
|
||||||
property<length> real-width: event.duration / visible-time * parent.width + min(real-x, 0);
|
property<length> real-width: event.duration / state.visible-time * parent.width + min(real-x, 0);
|
||||||
x: max(real-x, 0);
|
x: max(real-x, 0);
|
||||||
y: parent.height / 4;
|
y: parent.height / 4;
|
||||||
z: 1;
|
z: 1;
|
||||||
|
|
@ -110,7 +83,7 @@ export component Timeline inherits Rectangle {
|
||||||
y: root.height - self.height - timeline-event.height;
|
y: root.height - self.height - timeline-event.height;
|
||||||
text: timeline-event.x == timeline-event.real-x ?
|
text: timeline-event.x == timeline-event.real-x ?
|
||||||
TimeString.from(event.start) :
|
TimeString.from(event.start) :
|
||||||
TimeString.from(visible-offset - visible-time);
|
TimeString.from(visible-offset - state.visible-time);
|
||||||
visible: timeline-event.visible &&
|
visible: timeline-event.visible &&
|
||||||
(self.width * 2 < timeline-event.width ||
|
(self.width * 2 < timeline-event.width ||
|
||||||
(!end-txt.visible && self.width < timeline-event.width));
|
(!end-txt.visible && self.width < timeline-event.width));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue