Pseudo pseudorandom color picker

This commit is contained in:
Alexey 2025-09-18 16:30:37 +03:00
commit 218ee49a8b
7 changed files with 75 additions and 15 deletions

View file

@ -15,4 +15,4 @@ toml = "0.9.5"
slint-build = "1.12.1" slint-build = "1.12.1"
[profile.release] [profile.release]
opt-level = "s" opt-level = 3

View file

@ -1,5 +1,5 @@
use config::Config; use config::Config;
use std::path::PathBuf; use std::{hash::{DefaultHasher, Hash, Hasher}, path::PathBuf};
pub mod config; pub mod config;
pub mod log; pub mod log;
@ -13,3 +13,11 @@ pub fn load_config() -> Config {
} }
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.count_ones() / 4) as i32
}

View file

@ -3,7 +3,7 @@
use std::{error::Error, rc::Rc, sync::{Arc, Mutex}}; use std::{error::Error, rc::Rc, sync::{Arc, Mutex}};
use aliveline::{config::Config, load_config, log::{Event, Log}}; use aliveline::{color_id_from_name, config::Config, load_config, log::{Event, Log}};
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
use slint::{Model, ModelRc, SharedString, ToSharedString, VecModel, Weak}; use slint::{Model, ModelRc, SharedString, ToSharedString, VecModel, Weak};
use toml::value::{Date as TomlDate, Time}; use toml::value::{Date as TomlDate, Time};
@ -22,7 +22,8 @@ impl From<Event> for 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)
} }
} }
} }
@ -143,8 +144,9 @@ fn main() -> Result<(), Box<dyn Error>> {
let event = TimelineEvent { let event = TimelineEvent {
duration: 0, duration: 0,
finished: false, finished: false,
label: event_name, label: event_name.clone(),
start: offset start: offset,
color_id: color_id_from_name(event_name.to_string())
}; };
{ {
@ -177,8 +179,9 @@ fn main() -> Result<(), Box<dyn Error>> {
let new_event = TimelineEvent { let new_event = TimelineEvent {
duration: offset - event.start, duration: offset - event.start,
finished: true, finished: true,
label: event.label, label: event.label.clone(),
start: event.start start: event.start,
color_id: color_id_from_name(event.label.to_string())
}; };
{ {
@ -215,7 +218,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let maybe_unfinished_event = log_guard.events.iter().find(|event| !event.finished); let maybe_unfinished_event = log_guard.events.iter().find(|event| !event.finished);
match maybe_unfinished_event { match maybe_unfinished_event {
Some(unfinished_event) => Some(Event::new(unfinished_event.name.clone(), 0, 0, false)), Some(unfinished_event) => Some(Event::new(unfinished_event.name.clone(), 0, 0, false)),
None => None _ => None
} }
}; };

View file

@ -31,7 +31,6 @@ export component AppWindow inherits Window {
property<[string]> combo-spans: ["1 Hour", "4 Hours", "8 Hours", "24 Hours"]; property<[string]> combo-spans: ["1 Hour", "4 Hours", "8 Hours", "24 Hours"];
title: "Aliveline"; title: "Aliveline";
TabWidget { TabWidget {
Tab { Tab {
title: "Record"; title: "Record";

View file

@ -16,7 +16,6 @@ export component RecordWidget inherits VerticalBox {
property<string> event-name: ""; property<string> event-name: "";
property<bool> minimized: false; property<bool> minimized: false;
property<int> combo-index: 0; property<int> combo-index: 0;
tl := Timeline { tl := Timeline {
preferred-height: 100%; preferred-height: 100%;
updating: true; updating: true;

43
ui/theme.slint Normal file
View file

@ -0,0 +1,43 @@
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
];
}

View file

@ -1,8 +1,11 @@
import { Palette } from "theme.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
} }
global TimeString { global TimeString {
@ -26,6 +29,7 @@ global TimeString {
export component Timeline inherits Rectangle { export component Timeline inherits Rectangle {
callback new-day-started; callback new-day-started;
callback clicked <=> ta.clicked; callback clicked <=> ta.clicked;
background: Palette.background;
in-out property<bool> updating: true; in-out property<bool> updating: true;
in-out property<[TimelineEvent]> events: []; in-out property<[TimelineEvent]> events: [];
@ -52,7 +56,6 @@ export component Timeline inherits Rectangle {
preferred-height: 100%; preferred-height: 100%;
} }
background: gray;
border-width: 1px; border-width: 1px;
border-color: black; border-color: black;
Rectangle { Rectangle {
@ -63,19 +66,21 @@ export component Timeline inherits Rectangle {
height: parent.height / 2; height: parent.height / 2;
border-color: black; border-color: black;
border-width: 1px; border-width: 1px;
background: purple; background: Palette.timeline;
} }
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 - visible-time);
color: Palette.background-text;
} }
Text { Text {
x: parent.width - self.width; x: parent.width - self.width;
y: parent.height - self.height; y: parent.height - self.height;
text: TimeString.from(visible-offset); text: TimeString.from(visible-offset);
color: Palette.background-text;
} }
for event in events: timeline-event := Rectangle { for event in events: timeline-event := Rectangle {
@ -91,13 +96,14 @@ 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: red; background: Palette.event-colors[event.color-id];
Text { Text {
x: 0; x: 0;
y: -self.height; y: -self.height;
text: event.label; text: event.label;
visible: timeline-event.visible; visible: timeline-event.visible;
color: Palette.background-text;
} }
start-txt := Text { start-txt := Text {
x: 0; x: 0;
@ -108,6 +114,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];
} }
end-txt := Text { end-txt := Text {
x: timeline-event.width - self.width; x: timeline-event.width - self.width;
@ -116,6 +123,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];
} }
} }
@children @children