Compare commits
No commits in common. "master" and "v0.1" have entirely different histories.
12 changed files with 80 additions and 426 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -147,7 +147,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aliveline"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "aliveline"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -15,4 +15,4 @@ toml = "0.9.5"
|
|||
slint-build = "1.12.1"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
opt-level = "s"
|
||||
|
|
65
README.md
65
README.md
|
@ -1,45 +1,38 @@
|
|||
# Aliveline
|
||||
# Slint Rust Template
|
||||
|
||||
A template for a Rust application that's using [Slint](https://slint.rs/) for the user interface.
|
||||
|
||||
## About
|
||||
|
||||
Aliveline is a small app made with Rust + Slint to track daily activity on a timeline.
|
||||
All activity is saved into TOML logs, which are human readable/editable.
|
||||
|
||||
Aliveline currently supports Linux.
|
||||
|
||||
## Features
|
||||
|
||||
### Events
|
||||
Events are main timeline building blocks. They have name, start and end.
|
||||
Example of event in TOML format shown below:
|
||||
```toml
|
||||
[[events]]
|
||||
name = "test"
|
||||
start = 12:05:45
|
||||
end = 13:00:11
|
||||
finished = true
|
||||
```
|
||||
_Note: if event is not finished yet, it may have_ `end = 00:00:00`.
|
||||
|
||||
## Building
|
||||
Requirements:
|
||||
- Rust toolchain
|
||||
|
||||
Instructions:
|
||||
Run `cargo build --release`
|
||||
This template helps you get started developing a Rust application with Slint as toolkit
|
||||
for the user interface. It demonstrates the integration between the `.slint` UI markup and
|
||||
Rust code, how to react to callbacks, get and set properties, and use basic widgets.
|
||||
|
||||
## Usage
|
||||
|
||||
Just run `aliveline` by any preferred way, for example:
|
||||
```
|
||||
$ ./aliveline
|
||||
```
|
||||
1. Install Rust by following its [getting-started guide](https://www.rust-lang.org/learn/get-started).
|
||||
Once this is done, you should have the `rustc` compiler and the `cargo` build system installed in your `PATH`.
|
||||
2. Download and extract the [ZIP archive of this repository](https://github.com/slint-ui/slint-rust-template/archive/refs/heads/main.zip).
|
||||
3. Rename the extracted directory and change into it:
|
||||
```
|
||||
mv slint-rust-template-main my-project
|
||||
cd my-project
|
||||
```
|
||||
4. Build with `cargo`:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
5. Run the application binary:
|
||||
```
|
||||
cargo run
|
||||
```
|
||||
|
||||
## Configuration
|
||||
Aliveline tries to find config at `$XDG_CONFIG_DIR/aliveline/config.toml`.
|
||||
If config isn't found, or `$XDG_CONFIG_DIR` is not set, Aliveline uses default values.
|
||||
We recommend using an IDE for development, along with our [LSP-based IDE integration for `.slint` files](https://github.com/slint-ui/slint/blob/master/tools/lsp/README.md). You can also load this project directly in [Visual Studio Code](https://code.visualstudio.com) and install our [Slint extension](https://marketplace.visualstudio.com/items?itemName=Slint.slint).
|
||||
|
||||
See the example [config.toml](http://2ndbeam.ru/git/2ndbeam/aliveline/src/branch/master/config.toml) for default values.
|
||||
## Next Steps
|
||||
|
||||
## Contribution
|
||||
You can contribute to Aliveline by creating issue on this repository, then we'll discuss it.
|
||||
We hope that this template helps you get started, and that you enjoy exploring making user interfaces with Slint. To learn more
|
||||
about the Slint APIs and the `.slint` markup language, check out our [online documentation](https://slint.dev/docs).
|
||||
|
||||
Don't forget to edit this readme to replace it by yours, and edit the `name =` field in `Cargo.toml` to match the name of your
|
||||
project.
|
||||
|
|
53
config.toml
53
config.toml
|
@ -1,53 +0,0 @@
|
|||
# This is the default config for Aliveline.
|
||||
# Note: All colors are of format 0xAARRGGBB
|
||||
|
||||
# Path where logs are saved. May be relative to config dir or absolute.
|
||||
log_path = "logs"
|
||||
|
||||
# Colors used for events. For now Aliveline expects to have exactly 16 colors, but this is subject to change in future.
|
||||
event_colors = [
|
||||
0xff_97f9f9,
|
||||
0xff_a4def9,
|
||||
0xff_c1e0f7,
|
||||
0xff_cfbae1,
|
||||
0xff_c59fc9,
|
||||
0xff_4e3d42,
|
||||
0xff_c9d5b5,
|
||||
0xff_2d82b7,
|
||||
0xff_556f44,
|
||||
0xff_772e25,
|
||||
0xff_c44536,
|
||||
0xff_7c6a0a,
|
||||
0xff_babd8d,
|
||||
0xff_ffdac6,
|
||||
0xff_fa9500,
|
||||
0xff_eb6424
|
||||
]
|
||||
|
||||
# Colors used for event colors. Aliveline expects it to have same size as events.
|
||||
text_colors = [
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_ffffff,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_ffffff,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000,
|
||||
0xff_000000
|
||||
]
|
||||
|
||||
[colors]
|
||||
# Color behind the timeline
|
||||
background = 0xFF_808080
|
||||
# Color of the base timeline
|
||||
timeline = 0xFF_a9a9a9
|
||||
# Color of background text (timestamps, event names, etc.)
|
||||
background_text = 0xFF_000000
|
125
src/config.rs
125
src/config.rs
|
@ -2,138 +2,23 @@ use std::path::PathBuf;
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawColors {
|
||||
pub background: Option<u32>,
|
||||
pub timeline: Option<u32>,
|
||||
pub background_text: Option<u32>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Colors {
|
||||
pub background: u32,
|
||||
pub timeline: u32,
|
||||
pub background_text: u32
|
||||
}
|
||||
|
||||
impl Default for Colors {
|
||||
fn default() -> Self {
|
||||
Colors {
|
||||
background: 0xff_808080,
|
||||
timeline: 0xff_a9a9a9,
|
||||
background_text: 0xff_000000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawColors> for Colors {
|
||||
fn from(value: RawColors) -> Self {
|
||||
let default_colors: Colors = Default::default();
|
||||
Colors {
|
||||
background: value.background.unwrap_or(default_colors.background),
|
||||
timeline: value.timeline.unwrap_or(default_colors.timeline),
|
||||
background_text: value.background_text.unwrap_or(default_colors.background_text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawConfig {
|
||||
pub log_path: Option<PathBuf>,
|
||||
pub colors: Option<RawColors>,
|
||||
pub event_colors: Option<Vec<u32>>,
|
||||
pub text_colors: Option<Vec<u32>>
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
/// directory, where config is located
|
||||
#[serde(skip)]
|
||||
pub conf_path: PathBuf,
|
||||
pub log_path: PathBuf,
|
||||
pub colors: Colors,
|
||||
pub event_colors: Vec<u32>,
|
||||
pub text_colors: Vec<u32>
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let conf_path = PathBuf::new();
|
||||
let colors: Colors = Default::default();
|
||||
let event_colors: Vec<u32> = vec![
|
||||
0xff_97f9f9,
|
||||
0xff_a4def9,
|
||||
0xff_c1e0f7,
|
||||
0xff_cfbae1,
|
||||
0xff_c59fc9,
|
||||
0xff_4e3d42,
|
||||
0xff_c9d5b5,
|
||||
0xff_2d82b7,
|
||||
0xff_556f44,
|
||||
0xff_772e25,
|
||||
0xff_c44536,
|
||||
0xff_7c6a0a,
|
||||
0xff_babd8d,
|
||||
0xff_ffdac6,
|
||||
0xff_fa9500,
|
||||
0xff_eb6424
|
||||
];
|
||||
let text_colors: Vec<u32> = vec![
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xffffffff,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xffffffff,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000,
|
||||
0xff000000
|
||||
];
|
||||
Config {
|
||||
conf_path,
|
||||
log_path: PathBuf::from("./logs"),
|
||||
colors,
|
||||
event_colors,
|
||||
text_colors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawConfig> for Config {
|
||||
fn from(value: RawConfig) -> Self {
|
||||
let default_config: Config = Default::default();
|
||||
let colors: Colors = match value.colors {
|
||||
Some(raw_colors) => raw_colors.into(),
|
||||
None => default_config.colors.clone()
|
||||
};
|
||||
Config {
|
||||
conf_path: default_config.conf_path,
|
||||
log_path: value.log_path.unwrap_or(default_config.log_path),
|
||||
colors,
|
||||
event_colors: value.event_colors.unwrap_or(default_config.event_colors.clone()),
|
||||
text_colors: value.text_colors.unwrap_or(default_config.text_colors.clone())
|
||||
}
|
||||
}
|
||||
pub log_path: PathBuf
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(conf_path: PathBuf) -> Self {
|
||||
let conf_dir: PathBuf = conf_path.parent().unwrap().into();
|
||||
Config {
|
||||
conf_path: conf_dir,
|
||||
..Default::default()
|
||||
}
|
||||
Config { conf_path: conf_dir, log_path: PathBuf::from("./logs") }
|
||||
}
|
||||
|
||||
pub fn load(path: PathBuf) -> Self {
|
||||
if let Ok(toml_string) = std::fs::read_to_string(path.clone()) {
|
||||
let conf = toml::from_str::<RawConfig>(&toml_string);
|
||||
if let Ok(raw_conf) = conf {
|
||||
let mut conf: Config = raw_conf.into();
|
||||
let conf = toml::from_str::<Self>(&toml_string);
|
||||
if let Ok(mut conf) = conf {
|
||||
conf.conf_path = path.parent().unwrap().into();
|
||||
return conf;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use config::Config;
|
||||
use std::{hash::{DefaultHasher, Hash, Hasher}, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod config;
|
||||
pub mod log;
|
||||
|
@ -13,10 +13,3 @@ pub fn load_config() -> Config {
|
|||
}
|
||||
Config::new(PathBuf::from("./config.toml"))
|
||||
}
|
||||
|
||||
/// Get random-like color id in range 0..16 by computing string hash
|
||||
pub fn color_id_from_name(name: String) -> i32 {
|
||||
let mut s = DefaultHasher::new();
|
||||
name.hash(&mut s);
|
||||
let hash = s.finish();
|
||||
(hash % 16) as i32 }
|
||||
|
|
125
src/main.rs
125
src/main.rs
|
@ -3,9 +3,9 @@
|
|||
|
||||
use std::{error::Error, rc::Rc, sync::{Arc, Mutex}};
|
||||
|
||||
use aliveline::{color_id_from_name, config::Config, load_config, log::{Event, Log}};
|
||||
use aliveline::{config::Config, load_config, log::{Event, Log}};
|
||||
use chrono::{Datelike, Timelike};
|
||||
use slint::{Color, Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
|
||||
use slint::{Model, ModelRc, SharedString, ToSharedString, VecModel};
|
||||
use toml::value::{Date as TomlDate, Time};
|
||||
|
||||
slint::include_modules!();
|
||||
|
@ -22,8 +22,7 @@ impl From<Event> for TimelineEvent {
|
|||
start,
|
||||
duration: end - start,
|
||||
label: event.name.to_shared_string(),
|
||||
finished: event.finished,
|
||||
color_id: color_id_from_name(event.name)
|
||||
finished: event.finished
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,40 +46,6 @@ impl From<TimelineEvent> for Event {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>) {
|
||||
let ui = ui_weak.unwrap();
|
||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let events: Vec<TimelineEvent> = (*log_guard)
|
||||
.events
|
||||
.iter()
|
||||
.map(|event| TimelineEvent::from((*event).clone()))
|
||||
.collect();
|
||||
let in_progress = events.iter().any(|event| !event.finished);
|
||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||
ui.set_record_events(model);
|
||||
ui.set_in_progress(in_progress);
|
||||
}
|
||||
|
||||
fn load_colors(ui_weak: Weak<AppWindow>, config: Arc<Config>) {
|
||||
let ui = ui_weak.unwrap();
|
||||
let pal = ui.global::<Palette>();
|
||||
pal.set_background(Color::from_argb_encoded(config.colors.background));
|
||||
pal.set_timeline(Color::from_argb_encoded(config.colors.timeline));
|
||||
pal.set_background_text(Color::from_argb_encoded(config.colors.background_text));
|
||||
|
||||
// This looks like war crime
|
||||
let event_colors_rc: ModelRc<Color> = Rc::new(VecModel::from(
|
||||
config.event_colors.iter()
|
||||
.map(|value| Color::from_argb_encoded(*value)).collect::<Vec<Color>>()
|
||||
)).into();
|
||||
pal.set_event_colors(event_colors_rc);
|
||||
let event_text_rc: ModelRc<Color> = Rc::new(VecModel::from(
|
||||
config.text_colors.iter()
|
||||
.map(|value| Color::from_argb_encoded(*value)).collect::<Vec<Color>>()
|
||||
)).into();
|
||||
pal.set_event_text(event_text_rc);
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let ui = AppWindow::new()?;
|
||||
|
||||
|
@ -96,13 +61,23 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let config: Arc<Config> = Arc::new(load_config());
|
||||
let writing_log: Arc<Mutex<Log>> = Arc::new(Mutex::new(Log::load_from(&config, date)));
|
||||
|
||||
let ui_weak = ui.as_weak();
|
||||
let log = writing_log.clone();
|
||||
load_log(ui_weak, log);
|
||||
|
||||
let ui_weak = ui.as_weak();
|
||||
let config_arc = config.clone();
|
||||
load_colors(ui_weak, config_arc);
|
||||
{
|
||||
let ui_weak = ui.as_weak();
|
||||
let log = writing_log.clone();
|
||||
(move || {
|
||||
let ui = ui_weak.unwrap();
|
||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
let events: Vec<TimelineEvent> = (*log_guard)
|
||||
.events
|
||||
.iter()
|
||||
.map(|event| TimelineEvent::from((*event).clone()))
|
||||
.collect();
|
||||
let in_progress = events.iter().any(|event| !event.finished);
|
||||
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
|
||||
ui.set_record_events(model);
|
||||
ui.set_in_progress(in_progress);
|
||||
})()
|
||||
}
|
||||
|
||||
ui.invoke_update_record_offset(offset as i32);
|
||||
|
||||
|
@ -168,9 +143,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let event = TimelineEvent {
|
||||
duration: 0,
|
||||
finished: false,
|
||||
label: event_name.clone(),
|
||||
start: offset,
|
||||
color_id: color_id_from_name(event_name.to_string())
|
||||
label: event_name,
|
||||
start: offset
|
||||
};
|
||||
|
||||
{
|
||||
|
@ -191,8 +165,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let ui = ui_weak.unwrap();
|
||||
let events_rc = ui.get_record_events();
|
||||
let events = events_rc.as_any()
|
||||
.downcast_ref::<VecModel<TimelineEvent>>()
|
||||
.unwrap();
|
||||
.downcast_ref::<VecModel<TimelineEvent>>().unwrap();
|
||||
let offset = ui.get_record_offset();
|
||||
|
||||
let event_id = events.iter()
|
||||
|
@ -203,16 +176,15 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let new_event = TimelineEvent {
|
||||
duration: offset - event.start,
|
||||
finished: true,
|
||||
label: event.label.clone(),
|
||||
start: event.start,
|
||||
color_id: color_id_from_name(event.label.to_string())
|
||||
label: event.label,
|
||||
start: event.start
|
||||
};
|
||||
|
||||
{
|
||||
let mut log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
log_guard.events.push(Event::from(new_event.clone()));
|
||||
let index = log_guard.events.iter().position(|data| !data.finished).unwrap();
|
||||
log_guard.events.swap_remove(index);
|
||||
(*log_guard).events.push(Event::from(new_event.clone()));
|
||||
let index = (*log_guard).events.iter().position(|data| !data.finished).unwrap();
|
||||
(*log_guard).events.swap_remove(index);
|
||||
}
|
||||
|
||||
ui.invoke_save_log();
|
||||
|
@ -230,47 +202,6 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
}
|
||||
});
|
||||
|
||||
ui.on_new_day_started({
|
||||
let ui_weak = ui.as_weak();
|
||||
let log = writing_log.clone();
|
||||
move || {
|
||||
let ui = ui_weak.unwrap();
|
||||
|
||||
let new_event: Option<Event> = {
|
||||
let log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
|
||||
let maybe_unfinished_event = log_guard.events.iter().find(|event| !event.finished);
|
||||
match maybe_unfinished_event {
|
||||
Some(unfinished_event) => Some(Event::new(unfinished_event.name.clone(), 0, 0, false)),
|
||||
_ => None
|
||||
}
|
||||
};
|
||||
|
||||
ui.invoke_stop_event();
|
||||
|
||||
{
|
||||
let mut log_guard = log.lock().expect("Log shouldn't be used twice");
|
||||
log_guard.events.clear();
|
||||
let now = chrono::Local::now();
|
||||
|
||||
let date = TomlDate {
|
||||
year: now.year() as u16,
|
||||
month: now.month() as u8,
|
||||
day: now.day() as u8
|
||||
};
|
||||
|
||||
log_guard.date = date;
|
||||
log_guard.events.clear();
|
||||
|
||||
if let Some(event) = new_event {
|
||||
log_guard.events.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
load_log(ui.as_weak(), log.clone());
|
||||
ui.invoke_save_log();
|
||||
}
|
||||
});
|
||||
|
||||
ui.run()?;
|
||||
|
||||
|
|
|
@ -2,13 +2,11 @@ import { TabWidget } from "std-widgets.slint";
|
|||
import { RecordWidget } from "record.slint";
|
||||
import { ReviewWidget } from "review.slint";
|
||||
import { TimelineEvent } from "timeline.slint";
|
||||
export { Palette } from "theme.slint";
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
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 update-record-offset(int);
|
||||
callback save-log;
|
||||
|
||||
|
@ -32,6 +30,7 @@ export component AppWindow inherits Window {
|
|||
property<[string]> combo-spans: ["1 Hour", "4 Hours", "8 Hours", "24 Hours"];
|
||||
|
||||
title: "Aliveline";
|
||||
|
||||
TabWidget {
|
||||
Tab {
|
||||
title: "Record";
|
||||
|
|
|
@ -2,7 +2,6 @@ import { VerticalBox, LineEdit, Button, ComboBox } from "std-widgets.slint";
|
|||
import { Timeline } from "timeline.slint";
|
||||
|
||||
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);
|
||||
|
@ -13,29 +12,21 @@ export component RecordWidget inherits VerticalBox {
|
|||
in-out property events <=> tl.events;
|
||||
in property<[string]> combo-spans: [];
|
||||
in-out property<bool> in-progress: false;
|
||||
property<string> event-name: "";
|
||||
property<bool> minimized: false;
|
||||
property<int> combo-index: 0;
|
||||
property<string> event-name <=> le.text;
|
||||
|
||||
tl := Timeline {
|
||||
preferred-height: 100%;
|
||||
updating: true;
|
||||
clicked => {
|
||||
minimized = !minimized;
|
||||
}
|
||||
}
|
||||
if !minimized: GridLayout {
|
||||
GridLayout {
|
||||
spacing-vertical: 8px;
|
||||
spacing-horizontal: 16px;
|
||||
le := LineEdit {
|
||||
placeholder-text: "Event name";
|
||||
text: event-name;
|
||||
text: "Event name";
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
colspan: 2;
|
||||
row: 0;
|
||||
edited(text) => {
|
||||
event-name = text;
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: in-progress ? "Stop" : "Start";
|
||||
|
@ -66,12 +57,11 @@ export component RecordWidget inherits VerticalBox {
|
|||
}
|
||||
ComboBox {
|
||||
model: combo-spans;
|
||||
current-index: combo-index;
|
||||
current-index: 0;
|
||||
row: 2;
|
||||
col: 1;
|
||||
selected(current-value) => {
|
||||
root.update-visible-time(current-value);
|
||||
combo-index = self.current-index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export component ReviewWidget inherits VerticalBox {
|
|||
spacing-horizontal: 16px;
|
||||
Slider {
|
||||
minimum: visible-time;
|
||||
maximum: tl.max-offset;
|
||||
maximum: 24 * 3600;
|
||||
value: offset;
|
||||
row: 0;
|
||||
colspan: 2;
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
export global Palette {
|
||||
in-out property<color> background: gray;
|
||||
in-out property<color> timeline: darkgray;
|
||||
in-out property<color> background-text: black;
|
||||
// Note: these colors were almost randomly picked
|
||||
in-out property<[color]> event-colors: [
|
||||
#97f9f9,
|
||||
#a4def9,
|
||||
#c1e0f7,
|
||||
#cfbae1,
|
||||
#c59fc9,
|
||||
#4e3d42,
|
||||
#c9d5b5,
|
||||
#2d82b7,
|
||||
#556f44,
|
||||
#772e25,
|
||||
#c44536,
|
||||
#7c6a0a,
|
||||
#babd8d,
|
||||
#ffdac6,
|
||||
#fa9500,
|
||||
#eb6424
|
||||
];
|
||||
|
||||
in-out property <[color]> event-text: [
|
||||
#000000,
|
||||
#000000,
|
||||
#000000,
|
||||
#000000,
|
||||
#000000,
|
||||
#ffffff,
|
||||
#000000,
|
||||
#000000,
|
||||
#000000,
|
||||
#ffffff,
|
||||
#000000,
|
||||
#000000,
|
||||
#000000,
|
||||
#000000,
|
||||
#000000,
|
||||
#000000
|
||||
];
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
import { Palette } from "theme.slint";
|
||||
|
||||
export struct TimelineEvent {
|
||||
start: int,
|
||||
duration: int,
|
||||
finished: bool,
|
||||
label: string,
|
||||
color-id: int
|
||||
label: string
|
||||
}
|
||||
|
||||
global TimeString {
|
||||
|
@ -27,35 +24,21 @@ global TimeString {
|
|||
}
|
||||
|
||||
export component Timeline inherits Rectangle {
|
||||
callback new-day-started;
|
||||
callback clicked <=> ta.clicked;
|
||||
background: Palette.background;
|
||||
|
||||
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%;
|
||||
preferred-height: 100%;
|
||||
}
|
||||
|
||||
background: gray;
|
||||
border-width: 1px;
|
||||
border-color: black;
|
||||
Rectangle {
|
||||
|
@ -66,64 +49,40 @@ export component Timeline inherits Rectangle {
|
|||
height: parent.height / 2;
|
||||
border-color: black;
|
||||
border-width: 1px;
|
||||
background: Palette.timeline;
|
||||
background: purple;
|
||||
}
|
||||
|
||||
Text {
|
||||
x: 0;
|
||||
y: parent.height - self.height;
|
||||
text: TimeString.from(visible-offset - visible-time);
|
||||
color: Palette.background-text;
|
||||
}
|
||||
|
||||
Text {
|
||||
x: parent.width - self.width;
|
||||
y: parent.height - self.height;
|
||||
text: TimeString.from(visible-offset);
|
||||
color: Palette.background-text;
|
||||
}
|
||||
|
||||
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;
|
||||
width: event.finished ?
|
||||
min(parent.width - self.x, real-width) :
|
||||
(event.duration) / visible-time * parent.width + min(real-x, 0):
|
||||
parent.width - self.x;
|
||||
height: parent.height / 2;
|
||||
visible: self.width > 0 && self.real-x < parent.width;
|
||||
visible: self.real-x + self.width > 0 && self.real-x < parent.width;
|
||||
border-color: black;
|
||||
border-width: 1px;
|
||||
background: Palette.event-colors[event.color-id];
|
||||
|
||||
background: red;
|
||||
|
||||
Text {
|
||||
x: 0;
|
||||
y: -self.height;
|
||||
text: event.label;
|
||||
visible: timeline-event.visible;
|
||||
color: Palette.background-text;
|
||||
}
|
||||
start-txt := Text {
|
||||
x: 0;
|
||||
y: root.height - self.height - timeline-event.height;
|
||||
text: timeline-event.x == timeline-event.real-x ?
|
||||
TimeString.from(event.start) :
|
||||
TimeString.from(visible-offset - visible-time);
|
||||
visible: timeline-event.visible &&
|
||||
(self.width * 2 < timeline-event.width ||
|
||||
(!end-txt.visible && self.width < timeline-event.width));
|
||||
color: Palette.event-text[event.color-id];
|
||||
}
|
||||
end-txt := Text {
|
||||
x: timeline-event.width - self.width;
|
||||
y: root.height - self.height - timeline-event.height;
|
||||
text: timeline-event.x + timeline-event.real-width <= root.width ?
|
||||
TimeString.from(event.start + event.duration) :
|
||||
TimeString.from(visible-offset);
|
||||
visible: timeline-event.visible && timeline-event.width - self.width * 2 > 0;
|
||||
color: Palette.event-text[event.color-id];
|
||||
}
|
||||
}
|
||||
@children
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue