Add gamma control and night light (#38)
Add configurable night light, smooth gamma transitions, and shell controls. Update documentation and panel tests, and fix portal test startup synchronization.
This commit is contained in:
1 parent
9163374cdb
commit
2a400b26d0
34 files changed
+940
-20
No files matched your search
@@ -174,6 +174,13 @@ fn status_label(kind: Menu, s: &Snapshot) -> String {
|
||||
|b| format!("Battery: {}%, {}", b.percent, b.status),
|
||||
),
|
||||
Menu::System => "Control Center".into(),
|
||||
Menu::NightLight => {
|
||||
if s.night_light.as_ref().is_some_and(|s| s.enabled) {
|
||||
"Night light: on".into()
|
||||
} else {
|
||||
"Night light: off".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +198,10 @@ pub(super) fn audio_icon(volume: u8, muted: bool) -> &'static [u8] {
|
||||
|
||||
pub(super) fn status_icon(kind: Menu, s: &Snapshot) -> (&'static [u8], bool) {
|
||||
match kind {
|
||||
Menu::NightLight => (
|
||||
ferese_theme::icons::MOON,
|
||||
s.night_light.as_ref().is_some_and(|s| s.enabled),
|
||||
),
|
||||
Menu::Network => {
|
||||
let Some(n) = &s.network else {
|
||||
return (ferese_theme::icons::WIFI_OFF, false);
|
||||
|
||||
@@ -149,6 +149,7 @@ impl FereseShell {
|
||||
}
|
||||
|
||||
pub fn destroy_menu(&mut self) -> Task<Message> {
|
||||
self.night_light_temperature = None;
|
||||
let tooltip = self.dismiss_workspace_tooltip();
|
||||
crate::EFFECT_FRAME_PENDING.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
let Some(menu) = self.menu.take() else {
|
||||
@@ -167,6 +168,7 @@ impl FereseShell {
|
||||
}
|
||||
|
||||
pub fn close_menu(&mut self) -> Task<Message> {
|
||||
self.night_light_temperature = None;
|
||||
self.refresh_media_art(false);
|
||||
let Some(menu) = &mut self.menu else {
|
||||
return Task::none();
|
||||
@@ -210,6 +212,16 @@ impl FereseShell {
|
||||
}
|
||||
}
|
||||
Action::Brightness(v) => self.status.brightness = Some(*v),
|
||||
Action::NightLight(enabled) => {
|
||||
if let Some(status) = &mut self.status.night_light {
|
||||
status.enabled = *enabled;
|
||||
}
|
||||
}
|
||||
Action::NightLightTemperature(temperature) => {
|
||||
if let Some(status) = &mut self.status.night_light {
|
||||
status.temperature = *temperature;
|
||||
}
|
||||
}
|
||||
Action::Wifi(v) => {
|
||||
if let Some(n) = &mut self.status.network {
|
||||
n.enabled = *v;
|
||||
@@ -409,9 +421,11 @@ mod tests {
|
||||
},
|
||||
};
|
||||
assert!(anchor.belongs_to(&panel));
|
||||
panel.end.groups[0].items[2].visible = false;
|
||||
let items = &mut panel.end.groups[0].items;
|
||||
let network = items.iter().position(|item| item.id.0 == "network").unwrap();
|
||||
items[network].visible = false;
|
||||
assert!(!anchor.belongs_to(&panel));
|
||||
panel.end.groups[0].items.remove(2);
|
||||
panel.end.groups[0].items.remove(network);
|
||||
assert!(!anchor.belongs_to(&panel));
|
||||
panel.id = crate::panel::PanelId("other".into());
|
||||
let fallback = PopoverAnchor {
|
||||
|
||||
@@ -8,6 +8,7 @@ mod controls;
|
||||
mod lifecycle;
|
||||
mod media;
|
||||
mod network;
|
||||
mod night_light;
|
||||
mod system;
|
||||
|
||||
use bar::status_icon;
|
||||
@@ -32,6 +33,7 @@ struct MenuStyle {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Menu {
|
||||
NightLight,
|
||||
Activities,
|
||||
Workspaces,
|
||||
Network,
|
||||
@@ -51,7 +53,10 @@ impl Menu {
|
||||
let relevant = self == Self::System
|
||||
|| matches!(
|
||||
(self, &error.action),
|
||||
(Self::Network, Action::Wifi(_))
|
||||
(
|
||||
Self::NightLight,
|
||||
Action::NightLight(_) | Action::NightLightTemperature(_)
|
||||
) | (Self::Network, Action::Wifi(_))
|
||||
| (Self::Bluetooth, Action::Bluetooth(_))
|
||||
| (Self::Audio, Action::Volume(_) | Action::Mute(_))
|
||||
| (Self::Battery, Action::Brightness(_) | Action::PowerProfile(_))
|
||||
@@ -76,6 +81,7 @@ impl Menu {
|
||||
|
||||
fn title(self) -> &'static str {
|
||||
match self {
|
||||
Self::NightLight => "Night light",
|
||||
Self::Activities => "Activities",
|
||||
Self::Workspaces => "Workspaces",
|
||||
Self::System => "Control Center",
|
||||
@@ -93,6 +99,7 @@ impl Menu {
|
||||
|
||||
fn available(self, status: &Snapshot) -> bool {
|
||||
match self {
|
||||
Self::NightLight => true,
|
||||
Self::Network => status.network.is_some(),
|
||||
Self::Bluetooth => status.bluetooth.is_some(),
|
||||
Self::Audio => status.audio.is_some(),
|
||||
@@ -204,6 +211,7 @@ impl FereseShell {
|
||||
}
|
||||
let mut rows = match kind {
|
||||
Menu::System => system::view(self, rows, style, true),
|
||||
Menu::NightLight => night_light::view(self, rows, style),
|
||||
Menu::Network => network::view(self, rows, style),
|
||||
Menu::Bluetooth => bluetooth::view(self, rows, style, true),
|
||||
Menu::Audio => audio::view(self, rows, style, false),
|
||||
@@ -389,6 +397,7 @@ impl FereseShell {
|
||||
}
|
||||
rows = match kind {
|
||||
Menu::System => system::view(self, rows, style, false),
|
||||
Menu::NightLight => night_light::view(self, rows, style),
|
||||
Menu::Network => network::view(self, rows, style),
|
||||
Menu::Bluetooth => bluetooth::view(self, rows, style, false),
|
||||
Menu::Audio => audio::view(self, rows, style, false),
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use cosmic::widget::{column, slider};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(super) fn view<'a>(shell: &'a FereseShell, mut rows: MenuRows<'a>, style: MenuStyle) -> MenuRows<'a> {
|
||||
let Some(status) = &shell.status.night_light else {
|
||||
return rows.push(shell.render.text("Night light is unavailable").size(13));
|
||||
};
|
||||
rows = rows.push(toggle_row(
|
||||
shell.render,
|
||||
"Night light",
|
||||
status.enabled,
|
||||
Action::NightLight(!status.enabled),
|
||||
style.theme,
|
||||
style.opacity,
|
||||
));
|
||||
let temperature = shell.night_light_temperature.unwrap_or(status.temperature);
|
||||
let warmth = column![
|
||||
shell
|
||||
.render
|
||||
.text(format!("Color temperature · {temperature} K"))
|
||||
.size(13),
|
||||
slider(1000..=6500, temperature, |value| cosmic::Action::App(
|
||||
Message::NightLightTemperature(value)
|
||||
))
|
||||
.step(100u16)
|
||||
.on_release(cosmic::Action::App(Message::CommitNightLightTemperature)),
|
||||
shell.render.text("Lower temperatures are warmer").size(12),
|
||||
]
|
||||
.spacing(8);
|
||||
rows = rows.push(control_card(shell.render, warmth.into(), style.primary, style.opacity));
|
||||
if status.supported_outputs == 0 {
|
||||
rows = rows.push(shell.render.text("No display supports night light").size(13));
|
||||
} else if status.enabled && status.failed_outputs > 0 {
|
||||
rows = rows.push(
|
||||
shell
|
||||
.render
|
||||
.text("Night light could not be applied to some displays")
|
||||
.size(12),
|
||||
);
|
||||
}
|
||||
rows
|
||||
}
|
||||
@@ -128,6 +128,7 @@ pub(super) fn view<'a>(
|
||||
}
|
||||
rows = audio::view(shell, rows, style, true);
|
||||
rows = battery::brightness(shell, rows, style);
|
||||
rows = super::night_light::view(shell, rows, style);
|
||||
rows = notification_controls(shell, rows, style, true);
|
||||
rows
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user