mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 14:20:04 -04:00
Follow the System Theme in egui (#4860)
* Some initial progress towards #4490 This PR just moves `Theme` and the "follow system theme" settings to egui and adds `RawInput.system_theme`. A follow-up PR can then introduce the two separate `dark_mode_style` and `light_mode_style` fields on `Options`. <!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> * [x] I have followed the instructions in the PR template ### Breaking changes The options `follow_system_theme` and `default_theme` has been moved from `eframe` into `egui::Options`, settable with `ctx.options_mut` --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
use epaint::ColorImage;
|
||||
|
||||
use crate::{emath::*, Key, ViewportId, ViewportIdMap};
|
||||
use crate::{emath::*, Key, Theme, ViewportId, ViewportIdMap};
|
||||
|
||||
/// What the integrations provides to egui at the start of each frame.
|
||||
///
|
||||
@@ -73,6 +73,11 @@ pub struct RawInput {
|
||||
///
|
||||
/// False when the user alt-tab away from the application, for instance.
|
||||
pub focused: bool,
|
||||
|
||||
/// Does the OS use dark or light mode?
|
||||
///
|
||||
/// `None` means "don't know".
|
||||
pub system_theme: Option<Theme>,
|
||||
}
|
||||
|
||||
impl Default for RawInput {
|
||||
@@ -89,6 +94,7 @@ impl Default for RawInput {
|
||||
hovered_files: Default::default(),
|
||||
dropped_files: Default::default(),
|
||||
focused: true, // integrations opt into global focus tracking
|
||||
system_theme: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +123,7 @@ impl RawInput {
|
||||
hovered_files: self.hovered_files.clone(),
|
||||
dropped_files: std::mem::take(&mut self.dropped_files),
|
||||
focused: self.focused,
|
||||
system_theme: self.system_theme,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +141,7 @@ impl RawInput {
|
||||
mut hovered_files,
|
||||
mut dropped_files,
|
||||
focused,
|
||||
system_theme,
|
||||
} = newer;
|
||||
|
||||
self.viewport_id = viewport_ids;
|
||||
@@ -147,6 +155,7 @@ impl RawInput {
|
||||
self.hovered_files.append(&mut hovered_files);
|
||||
self.dropped_files.append(&mut dropped_files);
|
||||
self.focused = focused;
|
||||
self.system_theme = system_theme;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +198,7 @@ pub struct ViewportInfo {
|
||||
/// This should always be set, if known.
|
||||
///
|
||||
/// On web this takes browser scaling into account,
|
||||
/// and orresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript.
|
||||
/// and corresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript.
|
||||
pub native_pixels_per_point: Option<f32>,
|
||||
|
||||
/// Current monitor size in egui points.
|
||||
@@ -1044,6 +1053,7 @@ impl RawInput {
|
||||
hovered_files,
|
||||
dropped_files,
|
||||
focused,
|
||||
system_theme,
|
||||
} = self;
|
||||
|
||||
ui.label(format!("Active viwport: {viewport_id:?}"));
|
||||
@@ -1068,6 +1078,7 @@ impl RawInput {
|
||||
ui.label(format!("hovered_files: {}", hovered_files.len()));
|
||||
ui.label(format!("dropped_files: {}", dropped_files.len()));
|
||||
ui.label(format!("focused: {focused}"));
|
||||
ui.label(format!("system_theme: {system_theme:?}"));
|
||||
ui.scope(|ui| {
|
||||
ui.set_min_height(150.0);
|
||||
ui.label(format!("events: {events:#?}"))
|
||||
|
||||
@@ -460,7 +460,7 @@ pub use {
|
||||
layers::{LayerId, Order},
|
||||
layout::*,
|
||||
load::SizeHint,
|
||||
memory::{Memory, Options},
|
||||
memory::{Memory, Options, Theme},
|
||||
painter::Painter,
|
||||
response::{InnerResponse, Response},
|
||||
sense::Sense,
|
||||
|
||||
@@ -8,6 +8,9 @@ use crate::{
|
||||
ViewportId, ViewportIdMap, ViewportIdSet,
|
||||
};
|
||||
|
||||
mod theme;
|
||||
pub use theme::Theme;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The data that egui persists between frames.
|
||||
@@ -169,6 +172,21 @@ pub struct Options {
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub(crate) style: std::sync::Arc<Style>,
|
||||
|
||||
/// Whether to update the visuals according to the system theme or not.
|
||||
///
|
||||
/// Default: `true`.
|
||||
pub follow_system_theme: bool,
|
||||
|
||||
/// Which theme to use in case [`Self::follow_system_theme`] is set
|
||||
/// and egui fails to detect the system theme.
|
||||
///
|
||||
/// Default: [`crate::Theme::Dark`].
|
||||
pub fallback_theme: Theme,
|
||||
|
||||
/// Used to detect changes in system theme
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
system_theme: Option<Theme>,
|
||||
|
||||
/// Global zoom factor of the UI.
|
||||
///
|
||||
/// This is used to calculate the `pixels_per_point`
|
||||
@@ -262,6 +280,9 @@ impl Default for Options {
|
||||
|
||||
Self {
|
||||
style: Default::default(),
|
||||
follow_system_theme: true,
|
||||
fallback_theme: Theme::Dark,
|
||||
system_theme: None,
|
||||
zoom_factor: 1.0,
|
||||
zoom_with_keyboard: true,
|
||||
tessellation_options: Default::default(),
|
||||
@@ -278,11 +299,35 @@ impl Default for Options {
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput) {
|
||||
if self.follow_system_theme {
|
||||
let theme_from_visuals = Theme::from_dark_mode(self.style.visuals.dark_mode);
|
||||
let current_system_theme = self.system_theme.unwrap_or(theme_from_visuals);
|
||||
let new_system_theme = new_raw_input.system_theme.unwrap_or(self.fallback_theme);
|
||||
|
||||
// Only update the visuals if the system theme has changed.
|
||||
// This allows users to change the visuals without them
|
||||
// getting reset on the next frame.
|
||||
if current_system_theme != new_system_theme || self.system_theme.is_none() {
|
||||
self.system_theme = Some(new_system_theme);
|
||||
if theme_from_visuals != new_system_theme {
|
||||
let visuals = new_system_theme.default_visuals();
|
||||
std::sync::Arc::make_mut(&mut self.style).visuals = visuals;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Show the options in the ui.
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
style, // covered above
|
||||
style, // covered above
|
||||
follow_system_theme: _,
|
||||
fallback_theme: _,
|
||||
system_theme: _,
|
||||
zoom_factor: _, // TODO(emilk)
|
||||
zoom_with_keyboard,
|
||||
tessellation_options,
|
||||
@@ -665,6 +710,8 @@ impl Memory {
|
||||
|
||||
// self.interactions is handled elsewhere
|
||||
|
||||
self.options.begin_frame(new_raw_input);
|
||||
|
||||
self.focus
|
||||
.entry(self.viewport_id)
|
||||
.or_default()
|
||||
|
||||
29
crates/egui/src/memory/theme.rs
Normal file
29
crates/egui/src/memory/theme.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
/// Dark or Light theme.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum Theme {
|
||||
/// Dark mode: light text on a dark background.
|
||||
Dark,
|
||||
|
||||
/// Light mode: dark text on a light background.
|
||||
Light,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
/// Default visuals for this theme.
|
||||
pub fn default_visuals(self) -> crate::Visuals {
|
||||
match self {
|
||||
Self::Dark => crate::Visuals::dark(),
|
||||
Self::Light => crate::Visuals::light(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Chooses between [`Self::Dark`] or [`Self::Light`] based on a boolean value.
|
||||
pub fn from_dark_mode(dark_mode: bool) -> Self {
|
||||
if dark_mode {
|
||||
Self::Dark
|
||||
} else {
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user