Compare commits

...

1 commit

Author SHA1 Message Date
ba7bc67b6c feat: Reworked configuration
- Added colors.aliases table
- Colors can also be defined with aliases
- Event color is calculated with priorities
- Added paths table
- Renamed log_path as paths.logs
- Renamed colors.background_text as colors.text
- Added colors.fallback field
- Default values are now taken from source config.toml on compilation
2026-04-10 18:54:19 +03:00
13 changed files with 347 additions and 256 deletions

49
Cargo.lock generated
View file

@ -161,7 +161,7 @@ dependencies = [
"serde", "serde",
"slint", "slint",
"slint-build", "slint-build",
"toml", "toml 1.1.2+spec-1.1.0",
] ]
[[package]] [[package]]
@ -722,9 +722,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.59" version = "1.2.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -1685,9 +1685,9 @@ dependencies = [
[[package]] [[package]]
name = "gif" name = "gif"
version = "0.14.1" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [ dependencies = [
"color_quant", "color_quant",
"weezl", "weezl",
@ -1841,6 +1841,12 @@ dependencies = [
"foldhash 0.2.0", "foldhash 0.2.0",
] ]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -2333,12 +2339,12 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.1" version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.16.1", "hashbrown 0.17.0",
"serde", "serde",
"serde_core", "serde_core",
] ]
@ -2588,14 +2594,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.15" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
"libc", "libc",
"plain", "plain",
"redox_syscall 0.7.3", "redox_syscall 0.7.4",
] ]
[[package]] [[package]]
@ -3822,9 +3828,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
] ]
@ -4174,7 +4180,7 @@ dependencies = [
"regex", "regex",
"serde_json", "serde_json",
"tar", "tar",
"toml", "toml 0.9.12+spec-1.1.0",
] ]
[[package]] [[package]]
@ -4680,6 +4686,21 @@ dependencies = [
"winnow 0.7.15", "winnow 0.7.15",
] ]
[[package]]
name = "toml"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.1",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.7.5+spec-1.1.0" version = "0.7.5+spec-1.1.0"

View file

@ -7,7 +7,7 @@ edition = "2021"
chrono = "0.4.42" chrono = "0.4.42"
serde = "1.0.219" serde = "1.0.219"
slint = "1.15.1" slint = "1.15.1"
toml = "0.9.5" toml = "1.1.2"
[build-dependencies] [build-dependencies]
slint-build = "1.15.1" slint-build = "1.15.1"

View file

@ -1,5 +1,4 @@
fn main() { fn main() {
let mut style = String::from("cosmic"); let mut style = String::from("cosmic");
#[cfg(all(feature = "dark", feature = "light"))] #[cfg(all(feature = "dark", feature = "light"))]

View file

@ -1,53 +1,50 @@
# This is the default config for Aliveline. # Default Aliveline config
# Note: All colors are of format 0xAARRGGBB
# Path where logs are saved. May be relative to config dir or absolute. # Paths may be relative to config directory or absolute
log_path = "logs" [paths]
logs = "logs"
# Colors used for events. For now Aliveline expects to have exactly 16 colors, but this is subject to change in future. # Colors may be defined either as alias (string) or as 0xAARRGGBB (integer)
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. # Aliases must be declared here, otherwise fallback color is used
text_colors = [ [colors.aliases]
0xff_000000, background = 0xFF_808080
0xff_000000, timeline = 0xFF_A9A9A9
0xff_000000, black = 0xFF_000000
0xff_000000, white = 0xFF_FFFFFF
0xff_000000, "very aggressive pink color to use with fallback so you definitely notice it" = 0xFF_FF00E7
0xff_ffffff,
0xff_000000,
0xff_000000,
0xff_000000,
0xff_ffffff,
0xff_000000,
0xff_000000,
0xff_000000,
0xff_000000,
0xff_000000,
0xff_000000
]
[colors] [colors]
# Color behind the timeline # Timeline background
background = 0xFF_808080 background = "background"
# Color of the base timeline # Timeline foreground
timeline = 0xFF_a9a9a9 timeline = "timeline"
# Color of background text (timestamps, event names, etc.) # Background text (timestamps, event names, etc.)
background_text = 0xFF_000000 text = "black"
# Used when alias was not found
fallback = "very aggressive pink color to use with fallback so you definitely notice it"
# Event colors are chosen pseudorandomly from this array, respecting each color's priority
# Event color consists of:
# - Background color (default: black)
# - Text color (default: same as colors.text)
# - Priority (default: 1)
# Full example: { background = "color", text = "color", priority = 1337 }
events = [
{ background = 0xff_97f9f9 },
{ background = 0xff_a4def9 },
{ background = 0xff_c1e0f7 },
{ background = 0xff_cfbae1 },
{ background = 0xff_c59fc9 },
{ background = 0xff_4e3d42, text = "white" },
{ background = 0xff_c9d5b5 },
{ background = 0xff_2d82b7 },
{ background = 0xff_556f44 },
{ background = 0xff_772e25, text = "white" },
{ background = 0xff_c44536 },
{ background = 0xff_7c6a0a },
{ background = 0xff_babd8d },
{ background = 0xff_ffdac6 },
{ background = 0xff_fa9500 },
{ background = 0xff_eb6424 },
]

View file

@ -1,104 +0,0 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
#[serde(default)]
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,
}
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct Config {
/// directory, where config is located
#[serde(skip)]
pub conf_path: PathBuf,
pub log_path: PathBuf,
#[serde(default)]
pub colors: Colors,
#[serde(default)]
pub event_colors: Vec<u32>,
#[serde(default)]
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 Config {
pub fn new(conf_path: PathBuf) -> Self {
let conf_dir: PathBuf = conf_path.parent().unwrap().into();
Config {
conf_path: conf_dir,
..Default::default()
}
}
pub fn load(path: PathBuf) -> Option<Self> {
if let Ok(toml_string) = std::fs::read_to_string(path.clone()) {
if let Ok(mut conf) = toml::from_str::<Config>(&toml_string) {
conf.conf_path = path.parent().unwrap().into();
return Some(conf);
}
}
None
}
}

99
src/config/color.rs Normal file
View file

@ -0,0 +1,99 @@
use std::{collections::HashMap, hash::{DefaultHasher, Hash, Hasher}};
use serde::{Serialize, Deserialize};
use slint::Color;
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(untagged)]
pub enum ConfigColor {
Raw(u32),
Alias(String),
}
impl Default for ConfigColor {
fn default() -> Self {
Self::Raw(0xFF000000)
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(default)]
pub struct EventColor {
pub background: ConfigColor,
pub text: Option<ConfigColor>,
pub priority: u64,
}
impl Default for EventColor {
fn default() -> Self {
Self {
background: ConfigColor::default(),
text: None,
priority: 1,
}
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(default)]
pub struct Colors {
pub aliases: HashMap<String, u32>,
pub background: ConfigColor,
pub timeline: ConfigColor,
pub text: ConfigColor,
pub fallback: ConfigColor,
pub events: Vec<EventColor>,
}
impl Default for Colors {
fn default() -> Self {
super::default::default_config().colors.into()
}
}
#[inline(always)]
fn argb(value: u32) -> Color {
Color::from_argb_encoded(value)
}
impl Colors {
#[inline(always)]
pub fn fallback_color(&self) -> Color {
self.try_get_color(&self.fallback).unwrap_or_default()
}
pub fn try_get_color(&self, color: &ConfigColor) -> Option<Color> {
match color {
ConfigColor::Raw(color) => Some(argb(*color)),
ConfigColor::Alias(alias) => self.aliases.get(alias).map(|c| argb(*c)),
}
}
pub fn get_color(&self, color: &ConfigColor) -> Color {
self.try_get_color(color).unwrap_or_else(|| self.fallback_color())
}
pub fn event_color_for(&self, text: &str) -> &EventColor {
let priority_sum: u64 = self.events.iter().map(|e| e.priority).sum();
let mut s = DefaultHasher::new();
text.hash(&mut s);
let hash = s.finish();
let mut chosen = hash % priority_sum;
for (id, color) in self.events.iter().enumerate() {
match chosen.checked_sub(color.priority) {
Some(new_count) => {
chosen = new_count;
},
None => {
return &self.events[id];
},
}
}
unreachable!()
}
}

53
src/config/default.rs Normal file
View file

@ -0,0 +1,53 @@
use std::collections::HashMap;
use crate::config::color::{ConfigColor, EventColor};
use super::*;
const DEFAULT_CFG: &'static str = include_str!("../../config.toml");
#[derive(Deserialize)]
pub(super) struct DefaultConfig {
pub(super) colors: DefaultColors,
pub(super) paths: DefaultPaths,
}
#[derive(Deserialize)]
pub struct DefaultColors {
pub aliases: HashMap<String, u32>,
pub background: ConfigColor,
pub timeline: ConfigColor,
pub text: ConfigColor,
pub fallback: ConfigColor,
pub events: Vec<EventColor>,
}
impl From<DefaultColors> for Colors {
fn from(value: DefaultColors) -> Self {
Self {
aliases: value.aliases,
background: value.background,
timeline: value.timeline,
text: value.text,
fallback: value.fallback,
events: value.events
}
}
}
#[derive(Deserialize)]
pub struct DefaultPaths {
pub logs: PathBuf,
}
impl From<DefaultPaths> for Paths {
fn from(value: DefaultPaths) -> Self {
Self { logs: value.logs }
}
}
pub(super) fn default_config() -> DefaultConfig {
toml::de::from_str(DEFAULT_CFG).unwrap()
}

57
src/config/mod.rs Normal file
View file

@ -0,0 +1,57 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use color::Colors;
pub mod color;
mod default;
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(default)]
pub struct Paths {
pub logs: PathBuf,
}
impl Default for Paths {
fn default() -> Self {
default::default_config().paths.into()
}
}
#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, Debug)]
#[serde(default)]
pub struct Config {
/// directory, where config is located
#[serde(skip)]
pub conf_path: PathBuf,
pub colors: Colors,
pub paths: Paths,
}
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()
}
}
fn replace_missing_fields(&mut self) {
let default = Config::default();
if self.colors.events.len() == 0 {
self.colors.events = default.colors.events;
}
}
pub fn load(path: PathBuf) -> Option<Self> {
if let Ok(toml_string) = std::fs::read_to_string(path.clone()) {
if let Ok(mut conf) = toml::from_str::<Config>(&toml_string) {
conf.replace_missing_fields();
conf.conf_path = path.parent().unwrap().into();
return Some(conf);
}
}
None
}
}

View file

@ -1,5 +1,5 @@
use config::Config; use config::Config;
use std::{hash::{DefaultHasher, Hash, Hasher}, path::PathBuf}; use std::path::PathBuf;
pub mod config; pub mod config;
pub mod log; pub mod log;
@ -45,9 +45,3 @@ pub fn load_config() -> Config {
println!("Using config path ./config.toml"); println!("Using config path ./config.toml");
Config::new(PathBuf::from("./config.toml")) 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 }

View file

@ -46,12 +46,12 @@ impl Log {
} }
fn get_log_dir(config: &Config) -> PathBuf { fn get_log_dir(config: &Config) -> PathBuf {
if config.log_path.is_relative() { if config.paths.logs.is_relative() {
let mut path = config.conf_path.clone(); let mut path = config.conf_path.clone();
path.push(&config.log_path); path.push(&config.paths.logs);
return path; return path;
} else { } else {
return config.log_path.clone(); return config.paths.logs.clone();
} }
} }
@ -77,14 +77,14 @@ impl Event {
let start = Time { let start = Time {
hour: (start / 3600) as u8, hour: (start / 3600) as u8,
minute: ((start / 3600) / 60) as u8, minute: ((start / 3600) / 60) as u8,
second: (start % 60) as u8, second: Some((start % 60) as u8),
nanosecond: 0 nanosecond: None,
}; };
let end = Time { let end = Time {
hour: (end / 3600) as u8, hour: (end / 3600) as u8,
minute: ((end % 3600) / 60) as u8, minute: ((end % 3600) / 60) as u8,
second: (end % 60) as u8, second: Some((end % 60) as u8),
nanosecond: 0 nanosecond: None,
}; };
Event { name, start, end, finished } Event { name, start, end, finished }
} }

View file

@ -3,27 +3,40 @@
use std::{error::Error, rc::Rc, sync::{Arc, Mutex}}; 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, color::Colors}, load_config, log::{Event, Log}};
use chrono::{Datelike, Timelike}; 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};
slint::include_modules!(); slint::include_modules!();
impl From<Event> for TimelineEvent { impl TimelineEvent {
fn from(event: Event) -> Self { pub fn colors(colors: &Colors, text: &str) -> (Color, Color) {
let event_colors = colors.event_color_for(text);
let background_color = colors.get_color(&event_colors.background);
let text_color = match &event_colors.text {
Some(color) => colors.get_color(color),
None => colors.get_color(&colors.text),
};
(background_color, text_color)
}
pub fn from_event(event: Event, colors: &Colors) -> Self {
let start = (event.start.hour as i32) * 3600 let start = (event.start.hour as i32) * 3600
+ (event.start.minute as i32) * 60 + (event.start.minute as i32) * 60
+ (event.start.second as i32); + (event.start.second.unwrap() as i32);
let end = (event.end.hour as i32) * 3600 let end = (event.end.hour as i32) * 3600
+ (event.end.minute as i32) * 60 + (event.end.minute as i32) * 60
+ (event.end.second as i32); + (event.end.second.unwrap() as i32);
let (background_color, text_color) = TimelineEvent::colors(colors, event.name.as_str());
TimelineEvent { TimelineEvent {
start, start,
duration: end - start, duration: end - start,
label: event.name.to_shared_string(), label: event.name.to_shared_string(),
finished: event.finished, finished: event.finished,
color_id: color_id_from_name(event.name) background_color,
text_color,
} }
} }
} }
@ -33,27 +46,27 @@ impl From<TimelineEvent> for Event {
let start = Time { let start = Time {
hour: (event.start / 3600) as u8, hour: (event.start / 3600) as u8,
minute: ((event.start % 3600) / 60) as u8, minute: ((event.start % 3600) / 60) as u8,
second: (event.start % 60) as u8, second: Some((event.start % 60) as u8),
nanosecond: 0 nanosecond: None,
}; };
let endsecs = event.start + event.duration; let endsecs = event.start + event.duration;
let end = Time { let end = Time {
hour: (endsecs / 3600) as u8, hour: (endsecs / 3600) as u8,
minute: ((endsecs % 3600) / 60) as u8, minute: ((endsecs % 3600) / 60) as u8,
second: (endsecs % 60) as u8, second: Some((endsecs % 60) as u8),
nanosecond: 0 nanosecond: None,
}; };
Event { start, end, name: event.label.to_string(), finished: event.finished } Event { start, end, name: event.label.to_string(), finished: event.finished }
} }
} }
fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>) { fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>, config: Arc<Config>) {
let ui = ui_weak.unwrap(); let ui = ui_weak.unwrap();
let log_guard = log.lock().expect("Log shouldn't be used twice"); let log_guard = log.lock().expect("Log shouldn't be used twice");
let events: Vec<TimelineEvent> = (*log_guard) let events: Vec<TimelineEvent> = (*log_guard)
.events .events
.iter() .iter()
.map(|event| TimelineEvent::from((*event).clone())) .map(|event| TimelineEvent::from_event(event.clone(), &config.colors))
.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();
@ -67,21 +80,9 @@ fn load_log(ui_weak: Weak<AppWindow>, log: Arc<Mutex<Log>>) {
fn load_colors(ui_weak: Weak<AppWindow>, config: Arc<Config>) { fn load_colors(ui_weak: Weak<AppWindow>, config: Arc<Config>) {
let ui = ui_weak.unwrap(); let ui = ui_weak.unwrap();
let pal = ui.global::<Palette>(); let pal = ui.global::<Palette>();
pal.set_background(Color::from_argb_encoded(config.colors.background)); pal.set_background(config.colors.get_color(&config.colors.background));
pal.set_timeline(Color::from_argb_encoded(config.colors.timeline)); pal.set_timeline(config.colors.get_color(&config.colors.timeline));
pal.set_background_text(Color::from_argb_encoded(config.colors.background_text)); pal.set_background_text(config.colors.get_color(&config.colors.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>> { fn main() -> Result<(), Box<dyn Error>> {
@ -101,7 +102,8 @@ fn main() -> Result<(), Box<dyn Error>> {
let ui_weak = ui.as_weak(); let ui_weak = ui.as_weak();
let log = writing_log.clone(); let log = writing_log.clone();
load_log(ui_weak, log); let config_arc = config.clone();
load_log(ui_weak, log, config_arc);
let ui_weak = ui.as_weak(); let ui_weak = ui.as_weak();
let config_arc = config.clone(); let config_arc = config.clone();
@ -122,7 +124,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let events: Vec<TimelineEvent> = Log::load_from(&config, date) let events: Vec<TimelineEvent> = Log::load_from(&config, date)
.events .events
.iter() .iter()
.map(|event| TimelineEvent::from((*event).clone())) .map(|event| TimelineEvent::from_event(event.clone(), &config.colors))
.collect(); .collect();
let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into(); let model: ModelRc<TimelineEvent> = Rc::new(VecModel::from(events)).into();
let mut state = ui.get_review_state(); let mut state = ui.get_review_state();
@ -165,6 +167,7 @@ fn main() -> Result<(), Box<dyn Error>> {
ui.on_start_new_event({ ui.on_start_new_event({
let ui_weak = ui.as_weak(); let ui_weak = ui.as_weak();
let log = writing_log.clone(); let log = writing_log.clone();
let config = config.clone();
move |event_name: SharedString| { move |event_name: SharedString| {
let ui = ui_weak.unwrap(); let ui = ui_weak.unwrap();
@ -173,12 +176,15 @@ fn main() -> Result<(), Box<dyn Error>> {
.downcast_ref::<VecModel<TimelineEvent>>() .downcast_ref::<VecModel<TimelineEvent>>()
.unwrap(); .unwrap();
let (background_color, text_color) = TimelineEvent::colors(&config.colors, event_name.as_str());
let event = TimelineEvent { let event = TimelineEvent {
duration: 0, duration: 0,
finished: false, finished: false,
label: event_name.clone(), label: event_name.clone(),
start: state.offset, start: state.offset,
color_id: color_id_from_name(event_name.to_string()) background_color,
text_color,
}; };
{ {
@ -195,6 +201,7 @@ fn main() -> Result<(), Box<dyn Error>> {
ui.on_stop_event({ ui.on_stop_event({
let ui_weak = ui.as_weak(); let ui_weak = ui.as_weak();
let log = writing_log.clone(); let log = writing_log.clone();
let config = config.clone();
move || { move || {
let ui = ui_weak.unwrap(); let ui = ui_weak.unwrap();
let state = ui.get_record_state(); let state = ui.get_record_state();
@ -207,12 +214,16 @@ fn main() -> Result<(), Box<dyn Error>> {
.unwrap(); .unwrap();
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 (background_color, text_color) = TimelineEvent::colors(&config.colors, event.label.as_str());
let new_event = TimelineEvent { let new_event = TimelineEvent {
duration: state.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,
color_id: color_id_from_name(event.label.to_string()) background_color,
text_color,
}; };
{ {
@ -263,6 +274,7 @@ fn main() -> Result<(), Box<dyn Error>> {
ui.on_new_day_started({ ui.on_new_day_started({
let ui_weak = ui.as_weak(); let ui_weak = ui.as_weak();
let log = writing_log.clone(); let log = writing_log.clone();
let config = config.clone();
move || { move || {
let ui = ui_weak.unwrap(); let ui = ui_weak.unwrap();
@ -297,7 +309,7 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
} }
load_log(ui.as_weak(), log.clone()); load_log(ui.as_weak(), log.clone(), config.clone());
ui.invoke_save_log(); ui.invoke_save_log();
} }
}); });

View file

@ -2,42 +2,4 @@ export global Palette {
in-out property<color> background: gray; in-out property<color> background: gray;
in-out property<color> timeline: darkgray; in-out property<color> timeline: darkgray;
in-out property<color> background-text: black; 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
];
} }

View file

@ -6,7 +6,8 @@ export struct TimelineEvent {
duration: int, duration: int,
finished: bool, finished: bool,
label: string, label: string,
color-id: int, background-color: color,
text-color: color,
} }
export struct TimelineState { export struct TimelineState {
@ -69,7 +70,7 @@ export component Timeline inherits Rectangle {
visible: self.width > 0 && self.real-x < parent.width; visible: self.width > 0 && self.real-x < parent.width;
border-color: black; border-color: black;
border-width: 1px; border-width: 1px;
background: Palette.event-colors[event.color-id]; background: event.background-color;
Text { Text {
x: 0; x: 0;
@ -87,7 +88,7 @@ export component Timeline inherits Rectangle {
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));
color: Palette.event-text[event.color-id]; color: event.text-color;
} }
end-txt := Text { end-txt := Text {
x: timeline-event.width - self.width; x: timeline-event.width - self.width;
@ -96,7 +97,7 @@ export component Timeline inherits Rectangle {
TimeString.from(event.start + event.duration) : TimeString.from(event.start + event.duration) :
TimeString.from(visible-offset); TimeString.from(visible-offset);
visible: timeline-event.visible && timeline-event.width - self.width * 2 > 0; visible: timeline-event.visible && timeline-event.width - self.width * 2 > 0;
color: Palette.event-text[event.color-id]; color: event.text-color;
} }
} }
@children @children