ltk/theme/
prefs.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! Theme preference / mode types and per-asset specification structs.

use std::path::PathBuf;

use serde::{ Deserialize, Serialize };

use crate::types::Color;

// ─── Wallpaper / launcher ────────────────────────────────────────────────────

/// How a wallpaper image is fitted to its surface.
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub enum WallpaperFit
{
	/// Scale uniformly to fill the surface, cropping the overflow.
	Cover,
	/// Scale uniformly to fit inside the surface, letterboxing if needed.
	Contain,
	/// Scale non-uniformly to exactly match the surface.
	Stretch,
	/// Place at original size centered on the surface.
	Center,
	/// Repeat the image at original size to cover the surface.
	Tile,
}

impl Default for WallpaperFit
{
	fn default() -> Self { WallpaperFit::Cover }
}

/// Wallpaper backing for a single theme variant.
#[ derive( Debug, Clone ) ]
pub struct WallpaperSpec
{
	/// Absolute path to the image, or `None` for the built-in fallback that
	/// just paints [`crate::theme::Palette::bg`].
	pub path: Option<PathBuf>,
	pub fit:  WallpaperFit,
}

/// Styling for the application launcher surface.
#[ derive( Debug, Clone, Copy ) ]
pub struct LauncherSpec
{
	/// Background fill of the launcher panel.
	pub background:    Color,
	/// Outer corner radius of the launcher panel, in CSS pixels.
	pub border_radius: f32,
}

/// Styling for compositor / window-decoration controls.
#[ derive( Debug, Clone, Copy ) ]
pub struct WindowControlsSpec
{
	/// Background fill of the SSD title bar strip the controls sit on.
	pub bar_bg:         Color,
	/// Symbol colour for minimize / maximize / restore / close glyphs.
	pub icon:           Color,
	/// Button background while hovered.
	pub hover_bg:       Color,
	/// Button background while pressed.
	pub pressed_bg:     Color,
	/// Close-button background while hovered.
	pub close_hover_bg: Color,
	/// Close-button symbol colour while hovered.
	pub close_icon:     Color,
	/// Keyboard focus ring colour.
	pub focus_ring:     Color,
}

// ─── Mode and preference ─────────────────────────────────────────────────────

/// Which of the two variants a theme family resolves to.
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub enum ThemeMode
{
	Light,
	Dark,
}

impl Default for ThemeMode
{
	fn default() -> Self { ThemeMode::Light }
}

impl ThemeMode
{
	/// Auto-select light or dark from the local hour of day.
	///
	/// Dark is used between 19:00 and 06:59 local time, light otherwise.
	/// `hour` must be in `0..=23`.
	pub const fn from_hour( hour: u32 ) -> Self
	{
		if hour >= 19 || hour < 7 { ThemeMode::Dark } else { ThemeMode::Light }
	}
}

/// Shell-side persisted preference. `Auto` is resolved to a [`ThemeMode`]
/// before reaching ltk — the toolkit itself only knows about Light and Dark.
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub enum ThemePreference
{
	Light,
	Dark,
	Auto,
}

impl Default for ThemePreference
{
	fn default() -> Self { ThemePreference::Auto }
}

impl ThemePreference
{
	/// Resolve to a concrete mode given the current local hour `[0, 23]`.
	pub const fn resolve( self, hour: u32 ) -> ThemeMode
	{
		match self
		{
			ThemePreference::Light => ThemeMode::Light,
			ThemePreference::Dark  => ThemeMode::Dark,
			ThemePreference::Auto  => ThemeMode::from_hour( hour ),
		}
	}
}

#[ cfg( test ) ]
mod tests
{
	use super::*;

	#[ test ]
	fn preference_resolves_auto_by_hour()
	{
		assert_eq!( ThemePreference::Auto.resolve(  3 ), ThemeMode::Dark );
		assert_eq!( ThemePreference::Auto.resolve( 12 ), ThemeMode::Light );
		assert_eq!( ThemePreference::Auto.resolve( 22 ), ThemeMode::Dark );
		assert_eq!( ThemePreference::Light.resolve( 22 ), ThemeMode::Light );
	}
}