1
0
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:
Tau Gärtli
2024-08-06 20:17:51 +02:00
committed by GitHub
parent ed0254288a
commit 2dac4a4fc6
16 changed files with 129 additions and 137 deletions

View File

@@ -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:#?}"))

View File

@@ -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,

View File

@@ -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()

View 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
}
}
}