Compare commits

...
Author SHA1 Message Date
mollusk 06e97b9f50 Merge W16 room background fix
windows-build / windows-build (push) Has been cancelled
cargo-deny / cargo-deny (push) Has been cancelled
2026-06-20 17:57:20 -04:00
mollusk 601ec92181 Show custom background in room view 2026-06-20 17:57:16 -04:00
mollusk 713526b2a8 Merge W19 Ayu themes 2026-06-20 17:54:40 -04:00
mollusk 450121b591 Merge W16 custom backgrounds 2026-06-20 17:54:36 -04:00
mollusk eab9357f23 Add custom background settings controls 2026-06-20 17:54:27 -04:00
mollusk 57f21a0edf Add Ayu color themes 2026-06-20 17:54:23 -04:00
2 changed files with 97 additions and 5 deletions
+33 -1
View File
@@ -2432,6 +2432,35 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
.spacing(10)
.width(iced::Length::Fill);
let remove_background: Element<'_, AppMessage> = if state.config.background.is_some() {
button(text("Remove background").size(13))
.on_press(AppMessage::RemoveBackground)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(8)
.into()
} else {
iced::widget::Space::new().width(0.0).height(0.0).into()
};
let background_section = column![
row![
button(text("Choose image…").size(13))
.on_press(AppMessage::PickBackgroundFile)
.style(b_style(color_surface, color_blue, color_text, 6.0))
.padding(8),
remove_background,
].spacing(8),
text(format!("Background dimming: {:.0}%", state.config.background_dim * 100.0))
.size(11)
.color(color_subtext),
slider(0.0..=1.0, state.config.background_dim, AppMessage::SetBackgroundDim)
.step(0.05),
text("Set a picture from your computer as the app background. Auto-resized; a dimming overlay keeps text readable. Applies live.")
.size(11)
.color(color_subtext),
]
.spacing(10)
.width(iced::Length::Fill);
// Inline avatar chooser (W4): the monogram fallback plus the bundled
// presets, each a clickable tile. Same SelectAvatar message, applied live
// + persisted (and re-announced to the room).
@@ -2780,6 +2809,9 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
vertical_space(section_gap),
section_header("Theme"),
theme_section,
vertical_space(section_gap),
section_header("Background"),
background_section,
]
.spacing(10)
.width(iced::Length::Fill)
@@ -3725,7 +3757,7 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
.padding(15)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.style(c_style(color_crust, Color::TRANSPARENT, 0.0));
.style(c_style(root_bg, Color::TRANSPARENT, 0.0));
with_hotkey_info(
with_pixelpass_help(with_layout_picker(room.into(), state), state),
+64 -4
View File
@@ -60,6 +60,9 @@ pub enum AppTheme {
GruvboxDark,
SolarizedLight,
GruvboxLight,
AyuDark,
AyuMirage,
AyuLight,
}
/// Build a `Color` from a packed `0xRRGGBB` literal — keeps the palette tables
@@ -70,7 +73,7 @@ fn hex(c: u32) -> Color {
impl AppTheme {
/// Every theme, in picker order.
pub const ALL: [AppTheme; 10] = [
pub const ALL: [AppTheme; 13] = [
AppTheme::Mocha,
AppTheme::Macchiato,
AppTheme::Frappe,
@@ -81,6 +84,9 @@ impl AppTheme {
AppTheme::GruvboxDark,
AppTheme::SolarizedLight,
AppTheme::GruvboxLight,
AppTheme::AyuDark,
AppTheme::AyuMirage,
AppTheme::AyuLight,
];
/// Human-readable name for the picker.
@@ -96,6 +102,9 @@ impl AppTheme {
AppTheme::GruvboxDark => "Gruvbox Dark",
AppTheme::SolarizedLight => "Solarized Light",
AppTheme::GruvboxLight => "Gruvbox Light",
AppTheme::AyuDark => "Ayu Dark",
AppTheme::AyuMirage => "Ayu Mirage",
AppTheme::AyuLight => "Ayu Light",
}
}
@@ -103,7 +112,10 @@ impl AppTheme {
pub fn is_dark(self) -> bool {
!matches!(
self,
AppTheme::Latte | AppTheme::SolarizedLight | AppTheme::GruvboxLight
AppTheme::Latte
| AppTheme::SolarizedLight
| AppTheme::GruvboxLight
| AppTheme::AyuLight
)
}
@@ -120,6 +132,8 @@ impl AppTheme {
AppTheme::GruvboxDark => iced::Theme::GruvboxDark,
AppTheme::SolarizedLight => iced::Theme::SolarizedLight,
AppTheme::GruvboxLight => iced::Theme::GruvboxLight,
AppTheme::AyuDark | AppTheme::AyuMirage => iced::Theme::TokyoNight,
AppTheme::AyuLight => iced::Theme::Light,
}
}
@@ -281,6 +295,52 @@ impl AppTheme {
green: hex(0x79740e),
yellow: hex(0xb57614),
},
AppTheme::AyuDark => Palette {
crust: hex(0x06080a),
mantle: hex(0x0b0e14),
base: hex(0x0d1017),
surface: hex(0x1c222b),
overlay: hex(0x565b66),
text: hex(0xbfbdb6),
subtext: hex(0x9da1a6),
blue: hex(0xe6b450),
lavender: hex(0x59c2ff),
red: hex(0xf07178),
maroon: hex(0xff8f40),
green: hex(0xaad94c),
yellow: hex(0xffb454),
},
AppTheme::AyuMirage => Palette {
crust: hex(0x171b24),
mantle: hex(0x1a1f29),
base: hex(0x1f2430),
surface: hex(0x232834),
overlay: hex(0x707a8c),
text: hex(0xcccac2),
subtext: hex(0xa6abb4),
blue: hex(0xffcc66),
lavender: hex(0x73d0ff),
red: hex(0xf28779),
maroon: hex(0xffa759),
green: hex(0xd5ff80),
yellow: hex(0xffd173),
},
// Ayu Light's canonical orange is deepened for legibility on white.
AppTheme::AyuLight => Palette {
crust: hex(0xe6e9ec),
mantle: hex(0xf3f4f5),
base: hex(0xfcfcfc),
surface: hex(0xe8eaed),
overlay: hex(0x8a9199),
text: hex(0x5c6166),
subtext: hex(0x737980),
blue: hex(0xc7500e),
lavender: hex(0x399ee6),
red: hex(0xf07171),
maroon: hex(0xfa8d3e),
green: hex(0x86b300),
yellow: hex(0xff9940),
},
}
}
}
@@ -371,11 +431,11 @@ mod tests {
fn all_themes_distinct_and_labeled() {
// ALL covers exactly the variants once, each with a unique non-empty label
// and a distinct base colour (so swatches don't look identical).
assert_eq!(AppTheme::ALL.len(), 10);
assert_eq!(AppTheme::ALL.len(), 13);
let mut labels: Vec<&str> = AppTheme::ALL.iter().map(|t| t.label()).collect();
labels.sort_unstable();
labels.dedup();
assert_eq!(labels.len(), 10, "labels must be unique + non-empty");
assert_eq!(labels.len(), 13, "labels must be unique + non-empty");
assert!(labels.iter().all(|l| !l.is_empty()));
}