Add light mode

This commit is contained in:
Emil Ernerfeldt 2021-02-03 01:08:23 +01:00
parent c536e1b0da
commit a19fd7b780
12 changed files with 230 additions and 67 deletions

View file

@ -331,7 +331,7 @@ impl Prepared {
ui.painter().add(paint::Shape::Rect {
rect: outer_scroll_rect,
corner_radius,
fill: ui.visuals().dark_bg_color,
fill: ui.visuals().extreme_bg_color,
stroke: Default::default(),
// fill: visuals.bg_fill,
// stroke: visuals.bg_stroke,

View file

@ -38,6 +38,7 @@ impl State {
pub(crate) struct GridLayout {
ctx: CtxRef,
style: std::sync::Arc<Style>,
id: Id,
/// State previous frame (if any).
@ -61,6 +62,7 @@ impl GridLayout {
Self {
ctx: ui.ctx().clone(),
style: ui.style().clone(),
id,
prev_state,
curr_state: State::default(),
@ -125,8 +127,8 @@ impl GridLayout {
}
pub(crate) fn advance(&mut self, cursor: &mut Pos2, frame_rect: Rect, widget_rect: Rect) {
let debug_expand_width = self.ctx.style().visuals.debug_expand_width;
let debug_expand_height = self.ctx.style().visuals.debug_expand_height;
let debug_expand_width = self.style.visuals.debug_expand_width;
let debug_expand_height = self.style.visuals.debug_expand_height;
if debug_expand_width || debug_expand_height {
let rect = widget_rect;
let too_wide = rect.width() > self.prev_col_width(self.col);
@ -173,8 +175,12 @@ impl GridLayout {
let rect = Rect::from_min_size(*cursor, size);
let rect = rect.expand2(0.5 * self.spacing.y * Vec2::Y);
let rect = rect.expand2(2.0 * Vec2::X); // HACK: just looks better with some spacing on the sides
let color = Rgba::from_white_alpha(0.0075);
// let color = Rgba::from_black_alpha(0.2);
let color = if self.style.visuals.dark_mode {
Rgba::from_white_alpha(0.0075)
} else {
Rgba::from_black_alpha(0.075)
};
painter.rect_filled(rect, 2.0, color);
}
}

View file

@ -42,7 +42,7 @@ impl BarState {
/// In the latter case you may want to wrap it in `Frame`.
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) {
ui.horizontal(|ui| {
let mut style = ui.style().clone();
let mut style = (**ui.style()).clone();
style.spacing.button_padding = vec2(2.0, 0.0);
// style.visuals.widgets.active.bg_fill = Color32::TRANSPARENT;
style.visuals.widgets.active.bg_stroke = Stroke::none();
@ -102,7 +102,7 @@ fn menu_impl<'c>(
area.show(ui.ctx(), |ui| {
frame.show(ui, |ui| {
let mut style = ui.style().clone();
let mut style = (**ui.style()).clone();
style.spacing.button_padding = vec2(2.0, 0.0);
// style.visuals.widgets.active.bg_fill = Color32::TRANSPARENT;
style.visuals.widgets.active.bg_stroke = Stroke::none();

View file

@ -111,6 +111,10 @@ pub struct Interaction {
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Visuals {
/// If true, the visuals are overall dark with light text.
/// If false, the visuals are overall light with dark text.
pub dark_mode: bool,
/// Override default text color for all text.
///
/// This is great for setting the color of text for any widget.
@ -131,9 +135,10 @@ pub struct Visuals {
pub selection: Selection,
/// e.g. the background of the slider or text edit,
/// needs to look different from other interactive stuff.
pub dark_bg_color: Color32, // TODO: remove, rename, or clarify what it is for
/// Very dark or light color (for corresponding theme).
/// Used as the background of text edits, scroll bars and others things
/// that needs to look different from other interactive stuff.
pub extreme_bg_color: Color32,
/// The color used for `Hyperlink`,
pub hyperlink_color: Color32,
@ -286,17 +291,18 @@ impl Default for Interaction {
}
}
impl Default for Visuals {
fn default() -> Self {
impl Visuals {
pub fn dark() -> Self {
Self {
dark_mode: true,
override_text_color: None,
widgets: Default::default(),
selection: Default::default(),
dark_bg_color: Color32::from_gray(10),
widgets: Widgets::default(),
selection: Selection::default(),
extreme_bg_color: Color32::from_gray(10),
hyperlink_color: Color32::from_rgb(90, 170, 255),
code_bg_color: Color32::from_gray(64),
window_corner_radius: 10.0,
window_shadow: Shadow::big(),
window_shadow: Shadow::big_dark(),
resize_corner_size: 12.0,
text_cursor_width: 2.0,
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
@ -305,10 +311,29 @@ impl Default for Visuals {
debug_resize: false,
}
}
pub fn light() -> Self {
Self {
dark_mode: false,
widgets: Widgets::light(),
selection: Selection::light(),
extreme_bg_color: Color32::from_gray(235), // TODO: rename
hyperlink_color: Color32::from_rgb(0, 133, 218),
code_bg_color: Color32::from_gray(200),
window_shadow: Shadow::big_light(),
..Self::dark()
}
}
}
impl Default for Selection {
impl Default for Visuals {
fn default() -> Self {
Self::dark()
}
}
impl Selection {
fn dark() -> Self {
Self {
bg_fill: Rgba::from_rgb(0.0, 0.5, 1.0)
.additive()
@ -317,53 +342,106 @@ impl Default for Selection {
stroke: Stroke::new(1.0, Rgba::from_rgb(0.3, 0.6, 1.0)),
}
}
fn light() -> Self {
Self {
bg_fill: Rgba::from_rgb(0.0, 0.5, 1.0).multiply(0.5).into(),
stroke: Stroke::new(1.0, Rgba::from_rgb(0.3, 0.6, 1.0)),
}
}
}
impl Default for Widgets {
impl Default for Selection {
fn default() -> Self {
Self::dark()
}
}
impl Widgets {
pub fn dark() -> Self {
Self {
noninteractive: WidgetVisuals {
bg_stroke: Stroke::new(1.0, Color32::from_gray(65)), // window outline
bg_fill: Color32::from_gray(30), // window background
corner_radius: 4.0,
fg_stroke: Stroke::new(1.0, Color32::from_gray(160)), // text color
corner_radius: 4.0,
expansion: 0.0,
},
disabled: WidgetVisuals {
bg_fill: Color32::from_gray(40), // Should look grayed out
bg_stroke: Stroke::new(1.0, Color32::from_gray(70)),
corner_radius: 4.0,
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // Should look grayed out
corner_radius: 4.0,
expansion: 0.0,
},
inactive: WidgetVisuals {
bg_fill: Color32::from_gray(70),
bg_stroke: Default::default(),
corner_radius: 4.0,
fg_stroke: Stroke::new(1.0, Color32::from_gray(200)), // Should NOT look grayed out!
corner_radius: 4.0,
expansion: 0.0,
},
hovered: WidgetVisuals {
bg_fill: Color32::from_gray(80),
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
corner_radius: 4.0,
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
corner_radius: 4.0,
expansion: 1.0,
},
active: WidgetVisuals {
bg_fill: Color32::from_gray(90),
bg_stroke: Stroke::new(1.0, Color32::WHITE),
corner_radius: 4.0,
fg_stroke: Stroke::new(2.0, Color32::WHITE),
corner_radius: 4.0,
expansion: 2.0,
},
}
}
pub fn light() -> Self {
Self {
noninteractive: WidgetVisuals {
bg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // window outline
bg_fill: Color32::from_gray(220), // window background
fg_stroke: Stroke::new(1.0, Color32::from_gray(70)), // text color
corner_radius: 4.0,
expansion: 0.0,
},
disabled: WidgetVisuals {
bg_fill: Color32::from_gray(215), // Should look grayed out
bg_stroke: Stroke::new(1.0, Color32::from_gray(185)),
fg_stroke: Stroke::new(1.0, Color32::from_gray(115)), // Should look grayed out
corner_radius: 4.0,
expansion: 0.0,
},
inactive: WidgetVisuals {
bg_fill: Color32::from_gray(195),
bg_stroke: Default::default(),
fg_stroke: Stroke::new(1.0, Color32::from_gray(55)), // Should NOT look grayed out!
corner_radius: 4.0,
expansion: 0.0,
},
hovered: WidgetVisuals {
bg_fill: Color32::from_gray(175),
bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
fg_stroke: Stroke::new(2.0, Color32::BLACK),
corner_radius: 4.0,
expansion: 1.0,
},
active: WidgetVisuals {
bg_fill: Color32::from_gray(165),
bg_stroke: Stroke::new(1.0, Color32::BLACK),
fg_stroke: Stroke::new(2.0, Color32::BLACK),
corner_radius: 4.0,
expansion: 2.0,
},
}
}
}
impl Default for Widgets {
fn default() -> Self {
Self::dark()
}
}
// ----------------------------------------------------------------------------
@ -372,8 +450,6 @@ use crate::{widgets::*, Ui};
impl Style {
pub fn ui(&mut self, ui: &mut crate::Ui) {
crate::reset_button(ui, self);
let Self {
body_text_style,
spacing,
@ -381,6 +457,9 @@ impl Style {
visuals,
animation_time,
} = self;
visuals.light_dark_radio_buttons(ui);
ui.horizontal(|ui| {
ui.label("Default text style:");
for &value in &[TextStyle::Body, TextStyle::Monospace] {
@ -391,13 +470,13 @@ impl Style {
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
ui.add(Slider::f32(animation_time, 0.0..=1.0).text("animation_time"));
ui.vertical_centered(|ui| reset_button(ui, self));
}
}
impl Spacing {
pub fn ui(&mut self, ui: &mut crate::Ui) {
crate::reset_button(ui, self);
let Self {
item_spacing,
window_padding,
@ -422,13 +501,13 @@ impl Spacing {
ui.add(Slider::f32(icon_width, 0.0..=60.0).text("icon_width"));
ui.add(Slider::f32(icon_spacing, 0.0..=10.0).text("icon_spacing"));
ui.add(Slider::f32(tooltip_width, 0.0..=10.0).text("tooltip_width"));
ui.vertical_centered(|ui| reset_button(ui, self));
}
}
impl Interaction {
pub fn ui(&mut self, ui: &mut crate::Ui) {
crate::reset_button(ui, self);
let Self {
resize_grab_radius_side,
resize_grab_radius_corner,
@ -437,13 +516,13 @@ impl Interaction {
ui.add(
Slider::f32(resize_grab_radius_corner, 0.0..=20.0).text("resize_grab_radius_corner"),
);
ui.vertical_centered(|ui| reset_button(ui, self));
}
}
impl Widgets {
pub fn ui(&mut self, ui: &mut crate::Ui) {
crate::reset_button(ui, self);
let Self {
active,
hovered,
@ -472,6 +551,8 @@ impl Widgets {
ui.label("The style of a widget as you are clicking or dragging it.");
active.ui(ui)
});
ui.vertical_centered(|ui| reset_button(ui, self));
}
}
@ -503,14 +584,47 @@ impl WidgetVisuals {
}
impl Visuals {
pub fn ui(&mut self, ui: &mut crate::Ui) {
crate::reset_button(ui, self);
/// Show radio-buttons to switch between light and dark mode.
pub fn light_dark_radio_buttons(&mut self, ui: &mut crate::Ui) {
ui.group(|ui| {
ui.horizontal(|ui| {
ui.radio_value(self, Self::light(), "☀ Light");
ui.radio_value(self, Self::dark(), "🌙 Dark");
});
});
}
/// Show small toggle-button for light and dark mode.
#[must_use]
pub fn light_dark_small_toggle_button(&self, ui: &mut crate::Ui) -> Option<Self> {
#![allow(clippy::collapsible_if)]
if self.dark_mode {
if ui
.add(Button::new("").frame(false))
.on_hover_text("Switch to light mode")
.clicked()
{
return Some(Self::light());
}
} else {
if ui
.add(Button::new("🌙").frame(false))
.on_hover_text("Switch to dark mode")
.clicked()
{
return Some(Self::dark());
}
}
None
}
pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self {
dark_mode: _,
override_text_color: _,
widgets,
selection,
dark_bg_color,
extreme_bg_color,
hyperlink_color,
code_bg_color,
window_corner_radius,
@ -525,25 +639,42 @@ impl Visuals {
ui.collapsing("widgets", |ui| widgets.ui(ui));
ui.collapsing("selection", |ui| selection.ui(ui));
ui_color(ui, dark_bg_color, "dark_bg_color");
ui.group(|ui| {
ui.label("Window");
// Common shortcuts
ui_color(ui, &mut widgets.noninteractive.bg_fill, "Fill");
stroke_ui(ui, &mut widgets.noninteractive.bg_stroke, "Outline");
ui.add(Slider::f32(window_corner_radius, 0.0..=20.0).text("Corner Radius"));
shadow_ui(ui, window_shadow, "Shadow");
});
ui_color(
ui,
&mut widgets.noninteractive.fg_stroke.color,
"Text color",
);
ui_color(ui, extreme_bg_color, "extreme_bg_color");
ui_color(ui, hyperlink_color, "hyperlink_color");
ui_color(ui, code_bg_color, "code_bg_color");
ui.add(Slider::f32(window_corner_radius, 0.0..=20.0).text("window_corner_radius"));
shadow_ui(ui, window_shadow, "Window shadow:");
ui.add(Slider::f32(resize_corner_size, 0.0..=20.0).text("resize_corner_size"));
ui.add(Slider::f32(text_cursor_width, 0.0..=2.0).text("text_cursor_width"));
ui.add(Slider::f32(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin"));
ui.label("DEBUG:");
ui.checkbox(
debug_expand_width,
"Show which widgets make their parent wider",
);
ui.checkbox(
debug_expand_height,
"Show which widgets make their parent higher",
);
ui.checkbox(debug_resize, "Debug Resize");
ui.group(|ui| {
ui.label("DEBUG:");
ui.checkbox(
debug_expand_width,
"Show which widgets make their parent wider",
);
ui.checkbox(
debug_expand_height,
"Show which widgets make their parent higher",
);
ui.checkbox(debug_resize, "Debug Resize");
});
ui.vertical_centered(|ui| reset_button(ui, self));
}
}

View file

@ -1,6 +1,6 @@
// #![warn(missing_docs)]
use std::{hash::Hash, sync::Arc};
use std::hash::Hash;
use crate::{
color::*, containers::*, layout::*, mutex::MutexGuard, paint::text::Fonts, placer::Placer,
@ -42,7 +42,7 @@ pub struct Ui {
/// The `Style` (visuals, spacing, etc) of this ui.
/// Commonly many `Ui`:s share the same `Style`.
/// The `Ui` implements copy-on-write for this.
style: Arc<Style>,
style: std::sync::Arc<Style>,
/// Handles the `Ui` size and the placement of new widgets.
placer: Placer,
@ -93,18 +93,18 @@ impl Ui {
}
/// Style options for this `Ui` and its children.
pub fn style(&self) -> &Style {
pub fn style(&self) -> &std::sync::Arc<Style> {
&self.style
}
/// Mutably borrow internal `Style`.
/// Changes apply to this `Ui` and its subsequent children.
pub fn style_mut(&mut self) -> &mut Style {
Arc::make_mut(&mut self.style) // clone-on-write
std::sync::Arc::make_mut(&mut self.style) // clone-on-write
}
/// Changes apply to this `Ui` and its subsequent children.
pub fn set_style(&mut self, style: impl Into<Arc<Style>>) {
pub fn set_style(&mut self, style: impl Into<std::sync::Arc<Style>>) {
self.style = style.into();
}

View file

@ -306,7 +306,7 @@ impl<'a> Slider<'a> {
fill: ui.visuals().widgets.inactive.bg_fill,
// fill: visuals.bg_fill,
// fill: ui.visuals().dark_bg_color,
// fill: ui.visuals().extreme_bg_color,
stroke: Default::default(),
// stroke: visuals.bg_stroke,
// stroke: ui.visuals().widgets.inactive.bg_stroke,

View file

@ -245,14 +245,14 @@ impl<'t> Widget for TextEdit<'t> {
rect: frame_rect,
corner_radius: visuals.corner_radius,
// fill: ui.visuals().selection.bg_fill,
fill: ui.visuals().dark_bg_color,
fill: ui.visuals().extreme_bg_color,
stroke: ui.visuals().selection.stroke,
}
} else {
Shape::Rect {
rect: frame_rect,
corner_radius: visuals.corner_radius,
fill: ui.visuals().dark_bg_color,
fill: ui.visuals().extreme_bg_color,
stroke: visuals.bg_stroke, // TODO: we want to show something here, or a text-edit field doesn't "pop".
}
};

View file

@ -101,6 +101,13 @@ impl DemoWindows {
show_menu_bar(ui);
});
// Just get a background to put the windows on instead of using whatever the clear color is
let frame = egui::Frame {
fill: ctx.style().visuals.extreme_bg_color,
..egui::Frame::none()
};
egui::CentralPanel::default().frame(frame).show(ctx, |_| {});
self.windows(ctx);
}

View file

@ -59,7 +59,6 @@ impl FractalClock {
ui.expand_to_include_rect(painter.clip_rect());
Frame::popup(ui.style())
.fill(Rgba::from_luminance_alpha(0.02, 0.5).into())
.stroke(Stroke::none())
.show(ui, |ui| {
ui.set_max_width(270.0);

View file

@ -72,12 +72,13 @@ impl FrameHistory {
let mut shapes = vec![Shape::Rect {
rect,
corner_radius: style.corner_radius,
fill: ui.visuals().dark_bg_color,
fill: ui.visuals().extreme_bg_color,
stroke: ui.style().noninteractive().bg_stroke,
}];
let rect = rect.shrink(4.0);
let line_stroke = Stroke::new(1.0, Color32::from_additive_luminance(128));
let color = ui.visuals().text_color();
let line_stroke = Stroke::new(1.0, color);
if let Some(pointer_pos) = ui.input().pointer.tooltip_pos() {
if rect.contains(pointer_pos) {
@ -94,12 +95,12 @@ impl FrameHistory {
egui::Align2::LEFT_BOTTOM,
text,
TextStyle::Monospace,
Color32::WHITE,
color,
));
}
}
let circle_color = Color32::from_additive_luminance(196);
let circle_color = color;
let radius = 2.0;
let right_side_time = ui.input().time; // Time at right side of screen

View file

@ -69,6 +69,8 @@ impl epi::App for WrapApp {
// A menu-bar is a horizontal layout with some special styles applied.
// egui::menu::bar(ui, |ui| {
ui.horizontal_wrapped(|ui| {
dark_light_mode_switch(ui);
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
ui.separator();
@ -130,6 +132,15 @@ fn clock_button(ui: &mut egui::Ui, seconds_since_midnight: f64) -> egui::Respons
ui.add(egui::Button::new(time).text_style(egui::TextStyle::Monospace))
}
/// Show a button to switch to/from dark/light mode (globally).
fn dark_light_mode_switch(ui: &mut egui::Ui) {
let style: egui::Style = (*ui.ctx().style()).clone();
let new_visuals = style.visuals.light_dark_small_toggle_button(ui);
if let Some(visuals) = new_visuals {
ui.ctx().set_style(egui::Style { visuals, ..style });
}
}
// ----------------------------------------------------------------------------
/// How often we repaint the demo app by default

View file

@ -22,14 +22,22 @@ impl Shadow {
}
}
/// Windows
pub fn big() -> Self {
/// Subtle and nice on dark backgrounds
pub fn big_dark() -> Self {
Self {
extrusion: 32.0,
color: Color32::from_black_alpha(96),
}
}
/// Subtle and nice on white backgrounds
pub fn big_light() -> Self {
Self {
extrusion: 32.0,
color: Color32::from_black_alpha(40),
}
}
pub fn tessellate(&self, rect: emath::Rect, corner_radius: f32) -> Mesh {
// tessellator.clip_rect = clip_rect; // TODO: culling