Compare commits
No commits in common. "a64ed277f660420313d28601f55c3a5d5e8f4d03" and "365f77056ef9c8d52bf28fa9879ee75fd39002cb" have entirely different histories.
a64ed277f6
...
365f77056e
13 changed files with 1261 additions and 1823 deletions
2754
Cargo.lock
generated
2754
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
|
@ -1,20 +1,18 @@
|
|||
[package]
|
||||
name = "aliveline"
|
||||
version = "0.3.0"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.42"
|
||||
serde = "1.0.219"
|
||||
slint = "1.15.1"
|
||||
slint = "1.12.1"
|
||||
toml = "0.9.5"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = "1.15.1"
|
||||
slint-build = "1.12.1"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
||||
[features]
|
||||
light = []
|
||||
dark = []
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -34,22 +34,12 @@ finished = true
|
|||
_Note: if event is not finished yet, it may have_ `end = start`.
|
||||
|
||||
## Building
|
||||
|
||||
### Requirements
|
||||
Requirements:
|
||||
- Rust toolchain
|
||||
- Slint dependencies (see [Platforms](https://docs.slint.dev/latest/docs/slint/guide/platforms/desktop/) and [Backends & Renderers](https://docs.slint.dev/latest/docs/slint/guide/backends-and-renderers/backends_and_renderers/))
|
||||
|
||||
### Feature flags
|
||||
By default Aliveline compiles with theme autodetection, provided by Slint, which sometimes does not work on Linux.
|
||||
You can use these flags to compile Aliveline with selected theme:
|
||||
- `light`
|
||||
- `dark`
|
||||
|
||||
### Instructions
|
||||
Run `cargo build --release`
|
||||
Pass features in build command with `-F foo` or `--feature foo`
|
||||
Resulting binary will be located at `target/release/aliveline[.exe]`
|
||||
|
||||
Instructions:
|
||||
Just run `cargo build --release` and the resulting binary can be located at `target/release/aliveline[.exe]` if compilation succeeds.
|
||||
If compilation fails, double check that you have all required dependencies. If it still fails, file an issue on [Codeberg](https://codeberg.org/2ndbeam/aliveline/issues), including logs and system info.
|
||||
|
||||
## Usage
|
||||
|
|
|
|||
17
build.rs
17
build.rs
|
|
@ -1,18 +1,3 @@
|
|||
fn main() {
|
||||
|
||||
let mut style = String::from("cosmic");
|
||||
|
||||
#[cfg(all(feature = "dark", feature = "light"))]
|
||||
compile_error!("Features \"dark\" and \"light\" are mutually exclusive");
|
||||
|
||||
if cfg!(feature = "dark") {
|
||||
style.push_str("-dark");
|
||||
} else if cfg!(feature = "light") {
|
||||
style.push_str("-light");
|
||||
}
|
||||
|
||||
let config = slint_build::CompilerConfiguration::new()
|
||||
.with_style(style);
|
||||
|
||||
slint_build::compile_with_config("ui/app-window.slint", config).expect("Slint build failed");
|
||||
slint_build::compile("ui/app-window.slint").expect("Slint build failed");
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 115 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 25 KiB |
38
src/main.rs
38
src/main.rs
|
|
@ -8,6 +8,9 @@ use chrono::{Datelike, Timelike};
|
|||
use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
|
||||
use toml::value::{Date as TomlDate, Time};
|
||||
|
||||
const DEFAULT_WINDOW_WIDTH: f32 = 800.;
|
||||
const DEFAULT_WINDOW_HEIGHT: f32 = 600.;
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
impl From<Event> for TimelineEvent {
|
||||
|
|
@ -57,9 +60,7 @@ fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>) {
|
|||
.collect();
|
||||
let in_progress = events.iter().any(|event| !event.finished);
|
||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||
let mut state = ui.get_record_state();
|
||||
state.events = model;
|
||||
ui.set_record_state(state);
|
||||
ui.set_record_events(model);
|
||||
ui.set_in_progress(in_progress);
|
||||
ui.invoke_get_previous_event();
|
||||
}
|
||||
|
|
@ -108,6 +109,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
load_colors(ui_weak, config_arc);
|
||||
|
||||
ui.invoke_update_record_offset(offset as i32);
|
||||
ui.invoke_update_window_size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
|
||||
|
||||
ui.on_fetch_log({
|
||||
let config = config.clone();
|
||||
|
|
@ -125,9 +127,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
.map(|event| TimelineEvent::from((*event).clone()))
|
||||
.collect();
|
||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||
let mut state = ui.get_review_state();
|
||||
state.events = model;
|
||||
ui.set_review_state(state);
|
||||
ui.set_review_events(model);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -151,14 +151,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
.map(|h| h.parse::<i32>().unwrap())
|
||||
.unwrap();
|
||||
if is_record {
|
||||
let mut state = ui.get_record_state();
|
||||
state.visible_time = hours * 3600;
|
||||
ui.set_record_state(state);
|
||||
ui.set_record_visible_time(hours * 3600);
|
||||
} else {
|
||||
let mut state = ui.get_review_state();
|
||||
state.visible_time = hours * 3600;
|
||||
ui.set_review_state(state);
|
||||
};
|
||||
ui.set_review_visible_time(hours * 3600);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -167,17 +163,18 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let log = writing_log.clone();
|
||||
move |event_name: SharedString| {
|
||||
let ui = ui_weak.unwrap();
|
||||
|
||||
let state = ui.get_record_state();
|
||||
let events = state.events.as_any()
|
||||
|
||||
let events_rc = ui.get_record_events();
|
||||
let events = events_rc.as_any()
|
||||
.downcast_ref::<VecModel<TimelineEvent>>()
|
||||
.unwrap();
|
||||
let offset = ui.get_record_offset();
|
||||
|
||||
let event = TimelineEvent {
|
||||
duration: 0,
|
||||
finished: false,
|
||||
label: event_name.clone(),
|
||||
start: state.offset,
|
||||
start: offset,
|
||||
color_id: color_id_from_name(event_name.to_string())
|
||||
};
|
||||
|
||||
|
|
@ -197,10 +194,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let log = writing_log.clone();
|
||||
move || {
|
||||
let ui = ui_weak.unwrap();
|
||||
let state = ui.get_record_state();
|
||||
let events = state.events.as_any()
|
||||
let events_rc = ui.get_record_events();
|
||||
let events = events_rc.as_any()
|
||||
.downcast_ref::<VecModel<TimelineEvent>>()
|
||||
.unwrap();
|
||||
let offset = ui.get_record_offset();
|
||||
|
||||
let event_id = events.iter()
|
||||
.position(|data| !data.finished)
|
||||
|
|
@ -208,7 +206,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let event = events.row_data(event_id)
|
||||
.expect("stop-event called without unfinished events");
|
||||
let new_event = TimelineEvent {
|
||||
duration: state.offset - event.start,
|
||||
duration: offset - event.start,
|
||||
finished: true,
|
||||
label: event.label.clone(),
|
||||
start: event.start,
|
||||
|
|
|
|||
|
|
@ -1,106 +1,61 @@
|
|||
import { TabWidget, VerticalBox, ComboBox } from "std-widgets.slint";
|
||||
import { TabWidget } from "std-widgets.slint";
|
||||
import { RecordWidget } from "record.slint";
|
||||
import { ReviewWidget } from "review.slint";
|
||||
import { TimelineEvent, Timeline, TimelineState } from "timeline.slint";
|
||||
import { Const } from "global.slint";
|
||||
import { TimelineEvent } from "timeline.slint";
|
||||
export { Palette } from "theme.slint";
|
||||
|
||||
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;
|
||||
max-height: 2147483647px;
|
||||
|
||||
callback start-new-event <=> record.start-new-event;
|
||||
callback stop-event <=> record.stop-event;
|
||||
callback chain-event <=> record.chain-event;
|
||||
callback new-day-started <=> record.new-day-started;
|
||||
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 update-visible-time(bool, string);
|
||||
|
||||
in-out property record-state <=> record.state;
|
||||
|
||||
in-out property review-state <=> review.state;
|
||||
|
||||
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 review-offset <=> review.offset;
|
||||
in-out property review-visible-time <=> review.visible-time;
|
||||
|
||||
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";
|
||||
VerticalLayout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
tl := Timeline {
|
||||
preferred-height: 100%;
|
||||
min-height: 50px;
|
||||
state: in-record-mode ? record-state : review-state;
|
||||
clicked => {
|
||||
minimized = !minimized;
|
||||
TabWidget {
|
||||
Tab {
|
||||
title: "Record";
|
||||
record := RecordWidget {
|
||||
combo-spans: combo-spans;
|
||||
update-visible-time(time) => {
|
||||
root.update-visible-time(true, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
spacing: minimized ? 0 : 8px;
|
||||
record := RecordWidget {
|
||||
combo-spans: combo-spans;
|
||||
update-visible-time(time) => {
|
||||
root.update-visible-time(true, 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";
|
||||
Tab {
|
||||
title: "Review";
|
||||
review := ReviewWidget {
|
||||
combo-spans: combo-spans;
|
||||
update-visible-time(time) => {
|
||||
root.update-visible-time(false, time)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
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,25 +1,33 @@
|
|||
import { VerticalBox, LineEdit, Button, ComboBox } from "std-widgets.slint";
|
||||
import { Timeline, TimelineState } from "timeline.slint";
|
||||
import { Timeline } from "timeline.slint";
|
||||
|
||||
export component RecordWidget inherits VerticalLayout {
|
||||
export component RecordWidget inherits VerticalBox {
|
||||
callback new-day-started <=> tl.new-day-started;
|
||||
callback update-visible-time(string);
|
||||
callback start-new-event(string);
|
||||
callback chain-event(string);
|
||||
callback stop-event;
|
||||
callback get-previous-event();
|
||||
|
||||
in-out property<TimelineState> state;
|
||||
in-out property visible-time <=> tl.visible-time;
|
||||
in-out property updating <=> tl.updating;
|
||||
in-out property offset <=> tl.offset;
|
||||
in-out property events <=> tl.events;
|
||||
in property<[string]> combo-spans: [];
|
||||
in-out property<bool> in-progress: false;
|
||||
property<string> event-name: "";
|
||||
in property<string> previous-event-name: "";
|
||||
property<bool> minimized: false;
|
||||
property<int> combo-index: 0;
|
||||
in-out property <bool> minimized;
|
||||
|
||||
tl := Timeline {
|
||||
preferred-height: 100%;
|
||||
updating: true;
|
||||
clicked => {
|
||||
minimized = !minimized;
|
||||
}
|
||||
}
|
||||
if !minimized: GridLayout {
|
||||
spacing-vertical: 8px;
|
||||
spacing-horizontal: 16px;
|
||||
padding: 8px;
|
||||
le := LineEdit {
|
||||
placeholder-text: "Event name";
|
||||
text: event-name;
|
||||
|
|
@ -35,7 +43,6 @@ export component RecordWidget inherits VerticalLayout {
|
|||
text: in-progress ? "Stop" : "Start";
|
||||
row: 1;
|
||||
colspan: 2;
|
||||
primary: true;
|
||||
clicked => {
|
||||
if in-progress {
|
||||
root.stop-event();
|
||||
|
|
@ -49,7 +56,7 @@ export component RecordWidget inherits VerticalLayout {
|
|||
Button {
|
||||
text: "Chain";
|
||||
enabled: in-progress;
|
||||
col: 2;
|
||||
col: 3;
|
||||
row: 1;
|
||||
colspan: 2;
|
||||
clicked => {
|
||||
|
|
@ -60,7 +67,7 @@ export component RecordWidget inherits VerticalLayout {
|
|||
Button {
|
||||
text: previous-event-name == "" ? "Chain previous event (None)" : "Chain previous event (\{previous-event-name})";
|
||||
enabled: in-progress && previous-event-name != "";
|
||||
col: 4;
|
||||
col: 5;
|
||||
row: 1;
|
||||
colspan: 2;
|
||||
clicked => {
|
||||
|
|
@ -70,7 +77,7 @@ export component RecordWidget inherits VerticalLayout {
|
|||
}
|
||||
}
|
||||
Text {
|
||||
text: "Span: ";
|
||||
text: "Span:";
|
||||
font-size: 24px;
|
||||
row: 2;
|
||||
colspan: 3;
|
||||
|
|
|
|||
|
|
@ -1,59 +1,62 @@
|
|||
import { VerticalBox, LineEdit, Button, DatePickerPopup, ComboBox, Slider } from "std-widgets.slint";
|
||||
import { Timeline, TimelineState } from "timeline.slint";
|
||||
import { Const } from "global.slint";
|
||||
import { Timeline } from "timeline.slint";
|
||||
|
||||
export component ReviewWidget inherits VerticalLayout {
|
||||
export component ReviewWidget inherits VerticalBox {
|
||||
callback update-visible-time(string);
|
||||
callback fetch-log(int, int, int);
|
||||
|
||||
property<int> max-offset: 24 * 3600;
|
||||
property<int> current-year;
|
||||
property<int> current-month;
|
||||
property<int> current-day;
|
||||
in property<[string]> combo-spans: [];
|
||||
in-out property<TimelineState> state;
|
||||
in-out property<bool> minimized;
|
||||
in-out property<bool> is-active;
|
||||
in-out property visible-time <=> tl.visible-time;
|
||||
in-out property offset <=> tl.offset;
|
||||
in-out property events <=> tl.events;
|
||||
|
||||
if is-active: VerticalLayout {
|
||||
spacing: 8px;
|
||||
tl := Timeline {
|
||||
updating: false;
|
||||
}
|
||||
GridLayout {
|
||||
spacing-vertical: 8px;
|
||||
spacing-horizontal: 16px;
|
||||
Slider {
|
||||
minimum: state.visible-time;
|
||||
maximum: Const.max-offset;
|
||||
value: state.offset;
|
||||
minimum: visible-time;
|
||||
maximum: tl.max-offset;
|
||||
value: offset;
|
||||
row: 0;
|
||||
colspan: 2;
|
||||
changed(value) => {
|
||||
state.offset = value;
|
||||
offset = value;
|
||||
}
|
||||
}
|
||||
if !minimized: GridLayout {
|
||||
spacing-horizontal: 16px;
|
||||
padding: 8px;
|
||||
Text {
|
||||
text: "Day: \{current-day}/\{current-month}/\{current-year}";
|
||||
font-size: 24px;
|
||||
horizontal-alignment: right;
|
||||
Text {
|
||||
text: "Day: \{current-day}/\{current-month}/\{current-year}";
|
||||
font-size: 32px;
|
||||
horizontal-alignment: right;
|
||||
row: 1;
|
||||
}
|
||||
Button {
|
||||
text: "Select";
|
||||
clicked => {
|
||||
date-picker.show()
|
||||
}
|
||||
Button {
|
||||
primary: true;
|
||||
text: "Select";
|
||||
clicked => {
|
||||
date-picker.show()
|
||||
}
|
||||
col: 1;
|
||||
}
|
||||
Text {
|
||||
text: "Span: ";
|
||||
font-size: 24px;
|
||||
row: 1;
|
||||
horizontal-alignment: right;
|
||||
}
|
||||
ComboBox {
|
||||
model: combo-spans;
|
||||
current-index: 0;
|
||||
row: 1;
|
||||
col: 1;
|
||||
selected(current-value) => {
|
||||
root.update-visible-time(current-value);
|
||||
}
|
||||
row: 1;
|
||||
col: 1;
|
||||
}
|
||||
Text {
|
||||
text: "Span: ";
|
||||
font-size: 24px;
|
||||
row: 2;
|
||||
horizontal-alignment: right;
|
||||
}
|
||||
ComboBox {
|
||||
model: combo-spans;
|
||||
current-index: 0;
|
||||
row: 2;
|
||||
col: 1;
|
||||
selected(current-value) => {
|
||||
root.update-visible-time(current-value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,55 @@
|
|||
import { Palette } from "theme.slint";
|
||||
import { TimeString, Const } from "global.slint";
|
||||
|
||||
export struct TimelineEvent {
|
||||
start: int,
|
||||
duration: int,
|
||||
finished: bool,
|
||||
label: string,
|
||||
color-id: int,
|
||||
color-id: int
|
||||
}
|
||||
|
||||
export struct TimelineState {
|
||||
visible-time: int,
|
||||
offset: int,
|
||||
events: [TimelineEvent],
|
||||
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 component Timeline inherits Rectangle {
|
||||
callback new-day-started;
|
||||
callback clicked <=> ta.clicked;
|
||||
|
||||
background: Palette.background;
|
||||
|
||||
in-out property<TimelineState> state;
|
||||
property<int> visible-offset: max(state.offset, state.visible-time);
|
||||
out property<int> max-offset: Const.max-offset;
|
||||
|
||||
in-out property<bool> updating: true;
|
||||
in-out property<[TimelineEvent]> events: [];
|
||||
in-out property<int> visible-time: 3600;
|
||||
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 {
|
||||
preferred-width: 100%;
|
||||
|
|
@ -45,7 +72,7 @@ export component Timeline inherits Rectangle {
|
|||
Text {
|
||||
x: 0;
|
||||
y: parent.height - self.height;
|
||||
text: TimeString.from(visible-offset - state.visible-time);
|
||||
text: TimeString.from(visible-offset - visible-time);
|
||||
color: Palette.background-text;
|
||||
}
|
||||
|
||||
|
|
@ -56,9 +83,9 @@ export component Timeline inherits Rectangle {
|
|||
color: Palette.background-text;
|
||||
}
|
||||
|
||||
for event in state.events: timeline-event := Rectangle {
|
||||
property<length> real-x: ((state.visible-time - (visible-offset - event.start)) / state.visible-time) * parent.width;
|
||||
property<length> real-width: event.duration / state.visible-time * parent.width + min(real-x, 0);
|
||||
for event in events: timeline-event := Rectangle {
|
||||
property<length> real-x: ((visible-time - (visible-offset - event.start)) / visible-time) * parent.width;
|
||||
property<length> real-width: event.duration / visible-time * parent.width + min(real-x, 0);
|
||||
x: max(real-x, 0);
|
||||
y: parent.height / 4;
|
||||
z: 1;
|
||||
|
|
@ -83,7 +110,7 @@ export component Timeline inherits Rectangle {
|
|||
y: root.height - self.height - timeline-event.height;
|
||||
text: timeline-event.x == timeline-event.real-x ?
|
||||
TimeString.from(event.start) :
|
||||
TimeString.from(visible-offset - state.visible-time);
|
||||
TimeString.from(visible-offset - visible-time);
|
||||
visible: timeline-event.visible &&
|
||||
(self.width * 2 < timeline-event.width ||
|
||||
(!end-txt.visible && self.width < timeline-event.width));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue