feat(theme): per-app accent: pm gives window manifest accent before app runs (display ACCENT, refused after BUFFER); theme::with_accent keeps hue, shifts lightness until contrast passes, else theme accent; tally pink
This commit is contained in:
@@ -215,20 +215,24 @@ impl<F: Framebuffer, H: Host> DisplayServer<F, H> {
|
||||
/// A request through window `index`'s capability.
|
||||
fn window_call(&mut self, index: usize, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<Give>) {
|
||||
match (words[0], cap) {
|
||||
(w::BUFFER, _) => match self.host.buffer(index, self.pixels()) {
|
||||
Some(buffer) => {
|
||||
let (width, height) = self.window_size();
|
||||
([w::OK, width.raw() as usize, height.raw() as usize, 0], Some(Give::from(buffer)))
|
||||
(w::BUFFER, _) => {
|
||||
// The app runs: its accent is settled.
|
||||
self.windows.start(badge);
|
||||
match self.host.buffer(index, self.pixels()) {
|
||||
Some(buffer) => {
|
||||
let (width, height) = self.window_size();
|
||||
([w::OK, width.raw() as usize, height.raw() as usize, 0], Some(Give::from(buffer)))
|
||||
}
|
||||
None => ([MALFORMED, 0, 0, 0], None),
|
||||
}
|
||||
None => ([MALFORMED, 0, 0, 0], None),
|
||||
},
|
||||
}
|
||||
(w::ACCENT, _) if words[1] <= 0xff_ffff && self.windows.set_accent(badge, words[1] as u32) => ([w::OK, 0, 0, 0], None),
|
||||
(w::PRESENT, _) => {
|
||||
let (x, y, width, height) = w::unpack_rect([words[1], words[2]]);
|
||||
self.present(index, x, y, width, height);
|
||||
([w::OK, 0, 0, 0], None)
|
||||
}
|
||||
(w::SUBSCRIBE, Some(notify)) if self.windows.subscribe(badge, notify) => ([w::OK, 0, 0, 0], None),
|
||||
(w::THEME, _) => match self.themes.part(words[1]) {
|
||||
(w::THEME, _) => match self.themes.part(words[1], self.windows.accent(badge)) {
|
||||
Some([a, b, c]) => ([w::OK, a, b, c], None),
|
||||
None => ([MALFORMED, 0, 0, 0], None),
|
||||
},
|
||||
@@ -239,6 +243,7 @@ impl<F: Framebuffer, H: Host> DisplayServer<F, H> {
|
||||
}
|
||||
None => ([MALFORMED, 0, 0, 0], None),
|
||||
},
|
||||
(w::SUBSCRIBE, Some(notify)) if self.windows.subscribe(badge, notify) => ([w::OK, 0, 0, 0], None),
|
||||
(w::POLL, _) => (self.windows.poll(badge).unwrap_or([0; 4]), None),
|
||||
(w::CLOSE, _) => {
|
||||
self.close(index);
|
||||
|
||||
@@ -232,3 +232,23 @@ fn init_switches_the_theme_and_every_window_hears_of_it() {
|
||||
let bogus = w::pack_name(b"Nope");
|
||||
assert_eq!(s.handle_call(0, [w::SET_THEME, bogus[0], bogus[1], bogus[2]], None).0[0], MALFORMED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_launcher_sets_an_accent_and_it_is_in_the_theme() {
|
||||
let mut s = server();
|
||||
let app = open(&mut s, 2, b"Tally");
|
||||
let other = open(&mut s, 3, b"Snake");
|
||||
let pink = 0xe91e63;
|
||||
assert_eq!(s.handle_call(app, [w::ACCENT, pink, 0, 0], None).0[0], w::OK);
|
||||
assert_eq!(s.handle_call(app, [w::ACCENT, 0x00ff00, 0, 0], None).0[0], MALFORMED, "once");
|
||||
// The app starts: too late for it to choose one itself.
|
||||
s.handle_call(other, [w::BUFFER, 0, 0, 0], None);
|
||||
assert_eq!(s.handle_call(other, [w::ACCENT, pink, 0, 0], None).0[0], MALFORMED);
|
||||
let accent = |s: &mut Srv, badge| {
|
||||
let r = s.handle_call(badge, [w::THEME, 0, 0, 0], None).0;
|
||||
// Colour 3, the top of the theme's second word.
|
||||
theme::Rgba::rgb((r[3] >> 32) as u32 & 0xff_ffff)
|
||||
};
|
||||
assert_eq!(accent(&mut s, other), theme::Theme::WHIMSY_DARK.accent);
|
||||
assert_eq!(accent(&mut s, app), theme::with_accent(theme::Theme::WHIMSY_DARK, theme::Rgba::rgb(pink as u32)).accent);
|
||||
}
|
||||
|
||||
@@ -56,10 +56,14 @@ impl Themes {
|
||||
Some(changed)
|
||||
}
|
||||
|
||||
/// Part `part` of the theme in use, for a window: `changes | index <<
|
||||
/// 32`, and two of its words.
|
||||
pub fn part(&self, part: usize) -> Option<[usize; 3]> {
|
||||
let words = theme::wire::encode(self.active());
|
||||
/// Part `part` of the theme in use, for a window (with its app's
|
||||
/// accent, if any): `changes | index << 32`, and two of its words.
|
||||
pub fn part(&self, part: usize, accent: Option<u32>) -> Option<[usize; 3]> {
|
||||
let t = match accent {
|
||||
Some(rgb) => theme::with_accent(*self.active(), theme::Rgba::rgb(rgb)),
|
||||
None => *self.active(),
|
||||
};
|
||||
let words = theme::wire::encode(&t);
|
||||
let pair = words.get(part * 2..part * 2 + 2)?;
|
||||
Some([self.changes & 0xffff_ffff | self.active << 32, pair[0], pair[1]])
|
||||
}
|
||||
|
||||
@@ -50,6 +50,10 @@ struct Window<N> {
|
||||
name_len: usize,
|
||||
notify: Option<N>,
|
||||
queue: Queue,
|
||||
/// Its app's accent, `0xrrggbb`.
|
||||
accent: Option<u32>,
|
||||
/// Its buffer was asked for: the app runs, so the accent is settled.
|
||||
started: bool,
|
||||
}
|
||||
|
||||
pub struct Windows<N: Copy> {
|
||||
@@ -73,7 +77,7 @@ impl<N: Copy> Windows<N> {
|
||||
let mut n = [0u8; NAME_BYTES];
|
||||
let name_len = name.len().min(NAME_BYTES);
|
||||
n[..name_len].copy_from_slice(&name[..name_len]);
|
||||
self.slots[index] = Some(Window { badge, opener, name: n, name_len, notify: None, queue: Queue::new() });
|
||||
self.slots[index] = Some(Window { badge, opener, name: n, name_len, notify: None, queue: Queue::new(), accent: None, started: false });
|
||||
self.front = Some(index);
|
||||
Some((index, badge))
|
||||
}
|
||||
@@ -119,6 +123,27 @@ impl<N: Copy> Windows<N> {
|
||||
self.front
|
||||
}
|
||||
|
||||
/// Gives the window `badge` its app's accent: once, before it starts.
|
||||
pub fn set_accent(&mut self, badge: usize, rgb: u32) -> bool {
|
||||
let Some(w) = self.slot(badge).and_then(|i| self.slots[i].as_mut()) else { return false };
|
||||
let ok = !w.started && w.accent.is_none();
|
||||
if ok {
|
||||
w.accent = Some(rgb);
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
/// The window `badge`'s app is running (its buffer was asked for).
|
||||
pub fn start(&mut self, badge: usize) {
|
||||
if let Some(w) = self.slot(badge).and_then(|i| self.slots[i].as_mut()) {
|
||||
w.started = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accent(&self, badge: usize) -> Option<u32> {
|
||||
self.slots[self.slot(badge)?].as_ref()?.accent
|
||||
}
|
||||
|
||||
/// Tells `notify` when input for the window `badge` arrives.
|
||||
pub fn subscribe(&mut self, badge: usize, notify: N) -> bool {
|
||||
let Some(w) = self.slot(badge).and_then(|i| self.slots[i].as_mut()) else { return false };
|
||||
|
||||
Reference in New Issue
Block a user