Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revise configuration support #449

Merged
merged 20 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions crates/kas-core/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ bitflags! {
/// while others don't reqiure a context but do require that some *action*
/// is performed afterwards. This enum is used to convey that action.
///
/// An `Action` should be passed to a context: `cx.action(self.id(), action)`
/// (assuming `self` is a widget).
/// An `Action` produced at run-time should be passed to a context:
/// `cx.action(self.id(), action)` (assuming `self` is a widget).
/// An `Action` produced before starting the GUI may be discarded, for
/// example: `let _ = app.config_mut().font.set_size(24.0);`.
///
/// Two `Action` values may be combined via bit-or (`a | b`).
#[must_use]
Expand Down Expand Up @@ -41,18 +43,31 @@ bitflags! {
const SET_RECT = 1 << 8;
/// Resize all widgets in the window
const RESIZE = 1 << 9;
/// Update theme memory
/// Update [`Dimensions`](crate::theme::dimensions::Dimensions) instances
/// and theme configuration.
///
/// Implies [`Action::RESIZE`].
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
const THEME_UPDATE = 1 << 10;
/// Reload per-window cache of event configuration
///
/// Implies [`Action::UPDATE`].
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
const EVENT_CONFIG = 1 << 11;
/// Switch themes, replacing theme-window instances
///
/// Implies [`Action::RESIZE`].
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
const THEME_SWITCH = 1 << 12;
/// Reconfigure all widgets of the window
///
/// *Configuring* widgets assigns [`Id`](crate::Id) identifiers and calls
/// [`Events::configure`](crate::Events::configure).
///
/// Implies [`Action::UPDATE`] since widgets are updated on configure.
const RECONFIGURE = 1 << 16;
/// Update all widgets
///
Expand Down
37 changes: 23 additions & 14 deletions crates/kas-core/src/app/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
//! [`Application`] and supporting elements

use super::{AppData, AppGraphicsBuilder, AppState, Platform, ProxyAction, Result};
use crate::config::Options;
use crate::config::{Config, Options};
use crate::draw::{DrawShared, DrawSharedImpl};
use crate::event;
use crate::theme::{self, Theme, ThemeConfig};
use crate::theme::{self, Theme};
use crate::util::warn_about_error;
use crate::{impl_scope, Window, WindowId};
use std::cell::RefCell;
use std::cell::{Ref, RefCell, RefMut};
use std::rc::Rc;
use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy};

Expand All @@ -27,7 +26,7 @@ impl_scope! {
graphical: G,
theme: T,
options: Option<Options>,
config: Option<Rc<RefCell<event::Config>>>,
config: Option<Rc<RefCell<Config>>>,
}

impl Self {
Expand All @@ -54,29 +53,26 @@ impl_scope! {

/// Use the specified event `config`
///
/// This is a wrapper around [`Self::with_event_config_rc`].
/// This is a wrapper around [`Self::with_config_rc`].
///
/// If omitted, config is provided by [`Options::read_config`].
#[inline]
pub fn with_event_config(self, config: event::Config) -> Self {
self.with_event_config_rc(Rc::new(RefCell::new(config)))
pub fn with_config(self, config: Config) -> Self {
self.with_config_rc(Rc::new(RefCell::new(config)))
}

/// Use the specified event `config`
///
/// If omitted, config is provided by [`Options::read_config`].
#[inline]
pub fn with_event_config_rc(mut self, config: Rc<RefCell<event::Config>>) -> Self {
pub fn with_config_rc(mut self, config: Rc<RefCell<Config>>) -> Self {
self.config = Some(config);
self
}

/// Build with `data`
pub fn build<Data: AppData>(self, data: Data) -> Result<Application<Data, G, T>> {
let mut theme = self.theme;

let options = self.options.unwrap_or_else(Options::from_env);
options.init_theme_config(&mut theme)?;

let config = self.config.unwrap_or_else(|| match options.read_config() {
Ok(config) => Rc::new(RefCell::new(config)),
Expand All @@ -85,14 +81,15 @@ impl_scope! {
Default::default()
}
});
config.borrow_mut().init();

let el = EventLoopBuilder::with_user_event().build()?;

let mut draw_shared = self.graphical.build()?;
draw_shared.set_raster_config(theme.config().raster());
draw_shared.set_raster_config(config.borrow().font.raster());

let pw = PlatformWrapper(&el);
let state = AppState::new(data, pw, draw_shared, theme, options, config)?;
let state = AppState::new(data, pw, draw_shared, self.theme, options, config)?;

Ok(Application {
el,
Expand Down Expand Up @@ -166,6 +163,18 @@ where
&mut self.state.shared.draw
}

/// Access config
#[inline]
pub fn config(&self) -> Ref<Config> {
self.state.shared.config.borrow()
}

/// Access config mutably
#[inline]
pub fn config_mut(&mut self) -> RefMut<Config> {
self.state.shared.config.borrow_mut()
}

/// Access the theme by ref
#[inline]
pub fn theme(&self) -> &T {
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/app/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

use super::{AppData, AppGraphicsBuilder, AppState, Pending};
use super::{ProxyAction, Window};
use kas::theme::Theme;
use kas::{Action, WindowId};
use crate::theme::Theme;
use crate::{Action, WindowId};
use std::collections::HashMap;
use std::time::Instant;
use winit::event::{Event, StartCause};
Expand Down Expand Up @@ -215,7 +215,7 @@ where
elwt.set_control_flow(ControlFlow::Poll);
} else {
for (_, window) in self.windows.iter_mut() {
window.handle_action(&self.state, action);
window.handle_action(&mut self.state, action);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ mod test {
todo!()
}

fn set_raster_config(&mut self, _: &crate::theme::RasterConfig) {
fn set_raster_config(&mut self, _: &crate::config::RasterConfig) {
todo!()
}

Expand Down
36 changes: 10 additions & 26 deletions crates/kas-core/src/app/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
//! Shared state

use super::{AppData, AppGraphicsBuilder, Error, Pending, Platform};
use kas::config::Options;
use kas::draw::DrawShared;
use kas::theme::{Theme, ThemeControl};
use kas::util::warn_about_error;
use kas::{draw, messages::MessageStack, Action, WindowId};
use crate::config::{Config, Options};
use crate::draw::DrawShared;
use crate::theme::Theme;
use crate::util::warn_about_error;
use crate::{draw, messages::MessageStack, Action, WindowId};
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::VecDeque;
Expand All @@ -23,7 +23,7 @@ use std::task::Waker;
/// Application state used by [`AppShared`]
pub(crate) struct AppSharedState<Data: AppData, G: AppGraphicsBuilder, T: Theme<G::Shared>> {
pub(super) platform: Platform,
pub(super) config: Rc<RefCell<kas::event::Config>>,
pub(super) config: Rc<RefCell<Config>>,
#[cfg(feature = "clipboard")]
clipboard: Option<Clipboard>,
pub(super) draw: draw::SharedState<G::Shared>,
Expand Down Expand Up @@ -52,11 +52,11 @@ where
draw_shared: G::Shared,
mut theme: T,
options: Options,
config: Rc<RefCell<kas::event::Config>>,
config: Rc<RefCell<Config>>,
) -> Result<Self, Error> {
let platform = pw.platform();
let mut draw = kas::draw::SharedState::new(draw_shared);
theme.init(&mut draw);
let draw = kas::draw::SharedState::new(draw_shared);
theme.init(&config);

#[cfg(feature = "clipboard")]
let clipboard = match Clipboard::new() {
Expand Down Expand Up @@ -98,10 +98,7 @@ where
}

pub(crate) fn on_exit(&self) {
match self
.options
.write_config(&self.shared.config.borrow(), &self.shared.theme)
{
match self.options.write_config(&self.shared.config.borrow()) {
Ok(()) => (),
Err(error) => warn_about_error("Failed to save config", &error),
}
Expand Down Expand Up @@ -192,14 +189,6 @@ pub(crate) trait AppShared {
/// clipboard support.
fn set_primary(&mut self, content: String);

/// Adjust the theme
///
/// Note: theme adjustments apply to all windows, as does the [`Action`]
/// returned from the closure.
//
// TODO(opt): pass f by value, not boxed
fn adjust_theme<'s>(&'s mut self, f: Box<dyn FnOnce(&mut dyn ThemeControl) -> Action + 's>);

/// Access the [`DrawShared`] object
fn draw_shared(&mut self) -> &mut dyn DrawShared;

Expand Down Expand Up @@ -303,11 +292,6 @@ impl<Data: AppData, G: AppGraphicsBuilder, T: Theme<G::Shared>> AppShared
}
}

fn adjust_theme<'s>(&'s mut self, f: Box<dyn FnOnce(&mut dyn ThemeControl) -> Action + 's>) {
let action = f(&mut self.theme);
self.pending.push_back(Pending::Action(action));
}

fn draw_shared(&mut self) -> &mut dyn DrawShared {
&mut self.draw
}
Expand Down
60 changes: 34 additions & 26 deletions crates/kas-core/src/app/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
use super::common::WindowSurface;
use super::shared::{AppSharedState, AppState};
use super::{AppData, AppGraphicsBuilder, ProxyAction};
use kas::cast::{Cast, Conv};
use kas::draw::{color::Rgba, AnimationState, DrawSharedImpl};
use kas::event::{config::WindowConfig, ConfigCx, CursorIcon, EventState};
use kas::geom::{Coord, Rect, Size};
use kas::layout::SolveCache;
use kas::theme::{DrawCx, SizeCx, ThemeSize};
use kas::theme::{Theme, Window as _};
use kas::{autoimpl, messages::MessageStack, Action, Id, Layout, LayoutExt, Widget, WindowId};
use crate::cast::{Cast, Conv};
use crate::config::WindowConfig;
use crate::draw::{color::Rgba, AnimationState, DrawSharedImpl};
use crate::event::{ConfigCx, CursorIcon, EventState};
use crate::geom::{Coord, Rect, Size};
use crate::layout::SolveCache;
use crate::theme::{DrawCx, SizeCx, Theme, ThemeSize, Window as _};
use crate::{autoimpl, messages::MessageStack, Action, Id, Layout, LayoutExt, Widget, WindowId};
use std::mem::take;
use std::sync::Arc;
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -79,9 +79,11 @@ impl<A: AppData, G: AppGraphicsBuilder, T: Theme<G::Shared>> Window<A, G, T> {

// We cannot reliably determine the scale factor before window creation.
// A factor of 1.0 lets us estimate the size requirements (logical).
let mut theme_window = state.shared.theme.new_window(1.0);
let dpem = theme_window.size().dpem();
self.ev_state.update_config(1.0, dpem);
self.ev_state.update_config(1.0);

let config = self.ev_state.config();
let mut theme_window = state.shared.theme.new_window(config);

self.ev_state.full_configure(
theme_window.size(),
self.window_id,
Expand Down Expand Up @@ -122,10 +124,11 @@ impl<A: AppData, G: AppGraphicsBuilder, T: Theme<G::Shared>> Window<A, G, T> {
// Now that we have a scale factor, we may need to resize:
let scale_factor = window.scale_factor();
if scale_factor != 1.0 {
let sf32 = scale_factor as f32;
state.shared.theme.update_window(&mut theme_window, sf32);
let dpem = theme_window.size().dpem();
self.ev_state.update_config(sf32, dpem);
self.ev_state.update_config(scale_factor as f32);

let config = self.ev_state.config();
state.shared.theme.update_window(&mut theme_window, config);

let node = self.widget.as_node(&state.data);
let sizer = SizeCx::new(theme_window.size());
solve_cache = SolveCache::find_constraints(node, sizer);
Expand Down Expand Up @@ -226,13 +229,13 @@ impl<A: AppData, G: AppGraphicsBuilder, T: Theme<G::Shared>> Window<A, G, T> {
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
// Note: API allows us to set new window size here.
let scale_factor = scale_factor as f32;
self.ev_state.update_config(scale_factor as f32);

let config = self.ev_state.config();
state
.shared
.theme
.update_window(&mut window.theme_window, scale_factor);
let dpem = window.theme_window.size().dpem();
self.ev_state.update_config(scale_factor, dpem);
.update_window(&mut window.theme_window, config);

// NOTE: we could try resizing here in case the window is too
// small due to non-linear scaling, but it appears unnecessary.
Expand Down Expand Up @@ -298,12 +301,10 @@ impl<A: AppData, G: AppGraphicsBuilder, T: Theme<G::Shared>> Window<A, G, T> {
}

/// Handle an action (excludes handling of CLOSE and EXIT)
pub(super) fn handle_action(&mut self, state: &AppState<A, G, T>, mut action: Action) {
pub(super) fn handle_action(&mut self, state: &mut AppState<A, G, T>, mut action: Action) {
if action.contains(Action::EVENT_CONFIG) {
if let Some(ref mut window) = self.window {
let scale_factor = window.scale_factor() as f32;
let dpem = window.theme_window.size().dpem();
self.ev_state.update_config(scale_factor, dpem);
self.ev_state.update_config(window.scale_factor() as f32);
action |= Action::UPDATE;
}
}
Expand All @@ -312,14 +313,21 @@ impl<A: AppData, G: AppGraphicsBuilder, T: Theme<G::Shared>> Window<A, G, T> {
} else if action.contains(Action::UPDATE) {
self.update(state);
}
if action.contains(Action::THEME_UPDATE) {
if action.contains(Action::THEME_SWITCH) {
if let Some(ref mut window) = self.window {
let config = self.ev_state.config();
window.theme_window = state.shared.theme.new_window(config);
}
action |= Action::RESIZE;
} else if action.contains(Action::THEME_UPDATE) {
if let Some(ref mut window) = self.window {
let scale_factor = window.scale_factor() as f32;
let config = self.ev_state.config();
state
.shared
.theme
.update_window(&mut window.theme_window, scale_factor);
.update_window(&mut window.theme_window, config);
}
action |= Action::RESIZE;
}
if action.contains(Action::RESIZE) {
if let Some(ref mut window) = self.window {
Expand Down
Loading