Fix Settings rendering, theme accents and wallpaper loading
This commit is contained in:
1 parent
950ba723ab
commit
49e54f9a63
7 files changed
+461
-38
No files matched your search
@@ -15,6 +15,9 @@ mod visuals;
|
||||
mod wallpaper_controls;
|
||||
mod watch;
|
||||
|
||||
#[cfg(test)]
|
||||
mod scroll_tests;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
use super::*;
|
||||
use cosmic::Application;
|
||||
use cosmic::iced::advanced::renderer::{Headless, Renderer as _};
|
||||
use cosmic::iced::advanced::widget::{Operation, Tree};
|
||||
use cosmic::iced::advanced::{Layout, Shell, clipboard, layout, mouse, renderer};
|
||||
use cosmic::iced::{Color, Event, Font, Pixels, Point, Rectangle, Size, Vector, window};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ScrollPosition {
|
||||
offset: f32,
|
||||
content_height: f32,
|
||||
viewport_height: f32,
|
||||
}
|
||||
|
||||
impl Operation for ScrollPosition {
|
||||
fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
|
||||
operate(self);
|
||||
}
|
||||
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
id: Option<&cosmic::iced::advanced::widget::Id>,
|
||||
bounds: Rectangle,
|
||||
content: Rectangle,
|
||||
translation: Vector,
|
||||
_: &mut dyn cosmic::iced::advanced::widget::operation::Scrollable,
|
||||
) {
|
||||
if id == Some(&widget::Id::new("settings-content")) {
|
||||
self.offset = translation.y;
|
||||
self.content_height = content.height;
|
||||
self.viewport_height = bounds.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "release scroll/render sample; requires headless renderer backends"]
|
||||
fn appearance_scroll_preserves_progress_and_settles_visibility() {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let bounds = Rectangle::with_size(Size::new(960., 600.));
|
||||
let scale = 1.75;
|
||||
let physical = Size::new(1680, 1050);
|
||||
|
||||
for backend in ["wgpu", "tiny-skia"] {
|
||||
let mut renderer = runtime
|
||||
.block_on(<cosmic::Renderer as Headless>::new(
|
||||
Font::default(),
|
||||
Pixels(14.),
|
||||
Some(backend),
|
||||
))
|
||||
.expect("requested renderer");
|
||||
|
||||
for split in [false, true] {
|
||||
let source = format!("theme {{ split #{split}; family \"ferese-blue\"; }}");
|
||||
let mut app = App::init(
|
||||
Core::default(),
|
||||
(PathBuf::from("/unused/scroll-test.kdl"), Snapshot::parse(source), None),
|
||||
)
|
||||
.0;
|
||||
app.page = Page::Appearance;
|
||||
let theme = app.native_palette.native_theme();
|
||||
let mut tree: Option<Tree> = None;
|
||||
let mut previous = 0.;
|
||||
let mut render_times = Vec::new();
|
||||
let mut visibility_messages = 0;
|
||||
|
||||
for frame in 0..60 {
|
||||
let mut messages = Vec::new();
|
||||
let mut clipboard = clipboard::Null;
|
||||
let mut shell = Shell::new(&mut messages);
|
||||
let cursor = mouse::Cursor::Available(Point::new(680., 300.));
|
||||
let mut view = app.page_view();
|
||||
let tree = tree.get_or_insert_with(|| Tree::new(view.as_widget()));
|
||||
tree.diff(view.as_widget_mut());
|
||||
let node =
|
||||
view.as_widget_mut()
|
||||
.layout(tree, &renderer, &layout::Limits::new(Size::ZERO, bounds.size()));
|
||||
let event = if !(4..52).contains(&frame) {
|
||||
Event::Window(window::Event::RedrawRequested(std::time::Instant::now()))
|
||||
} else {
|
||||
Event::Mouse(mouse::Event::WheelScrolled {
|
||||
delta: mouse::ScrollDelta::Pixels {
|
||||
x: 0.,
|
||||
y: if frame < 28 { -12. } else { 12. },
|
||||
},
|
||||
})
|
||||
};
|
||||
view.as_widget_mut().update(
|
||||
tree,
|
||||
&event,
|
||||
Layout::new(&node),
|
||||
cursor,
|
||||
&renderer,
|
||||
&mut clipboard,
|
||||
&mut shell,
|
||||
&bounds,
|
||||
);
|
||||
|
||||
// Sensors observe real redraw events as well as wheel input.
|
||||
if (4..52).contains(&frame) {
|
||||
view.as_widget_mut().update(
|
||||
tree,
|
||||
&Event::Window(window::Event::RedrawRequested(std::time::Instant::now())),
|
||||
Layout::new(&node),
|
||||
cursor,
|
||||
&renderer,
|
||||
&mut clipboard,
|
||||
&mut shell,
|
||||
&bounds,
|
||||
);
|
||||
}
|
||||
|
||||
let mut position = ScrollPosition::default();
|
||||
view.as_widget_mut()
|
||||
.operate(tree, Layout::new(&node), &renderer, &mut position);
|
||||
assert!(position.content_height > position.viewport_height);
|
||||
if (4..28).contains(&frame) {
|
||||
assert!(
|
||||
position.offset > previous
|
||||
|| position.offset >= position.content_height - position.viewport_height - 0.5,
|
||||
"scroll stopped at {position:?}"
|
||||
);
|
||||
}
|
||||
|
||||
if (28..52).contains(&frame) {
|
||||
assert!(
|
||||
position.offset < previous || position.offset == 0.,
|
||||
"reverse scroll stopped at {position:?}"
|
||||
);
|
||||
}
|
||||
|
||||
previous = position.offset;
|
||||
renderer.reset(bounds);
|
||||
view.as_widget().draw(
|
||||
tree,
|
||||
&mut renderer,
|
||||
&theme,
|
||||
&renderer::Style {
|
||||
text_color: Color::WHITE,
|
||||
icon_color: Color::WHITE,
|
||||
scale_factor: scale as f64,
|
||||
},
|
||||
Layout::new(&node),
|
||||
cursor,
|
||||
&bounds,
|
||||
);
|
||||
drop(view);
|
||||
let started = std::time::Instant::now();
|
||||
std::hint::black_box(Headless::screenshot(
|
||||
&mut renderer,
|
||||
physical,
|
||||
scale,
|
||||
theme.cosmic().bg_color().into(),
|
||||
));
|
||||
if (4..52).contains(&frame) {
|
||||
render_times.push(started.elapsed().as_micros());
|
||||
}
|
||||
|
||||
for message in messages {
|
||||
if matches!(message, Message::RowVisibility(..)) {
|
||||
visibility_messages += 1;
|
||||
assert!(frame < 56, "visibility kept changing after scrolling stopped");
|
||||
}
|
||||
|
||||
let _ = app.update(message);
|
||||
}
|
||||
}
|
||||
|
||||
render_times.sort_unstable();
|
||||
eprintln!(
|
||||
"backend={backend} split={split} render_median_us={} p95_us={} visibility_messages={visibility_messages}",
|
||||
render_times[render_times.len() / 2],
|
||||
render_times[render_times.len() * 95 / 100]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ pub fn choose_family(id: &str, appearance: Option<ferese_config::theme::Appearan
|
||||
let kinds: &[&str] = match appearance {
|
||||
Some(ferese_config::theme::Appearance::Light) => &["light"],
|
||||
Some(ferese_config::theme::Appearance::Dark) => &["dark"],
|
||||
None => &[],
|
||||
None => &["light", "dark"],
|
||||
};
|
||||
let mut edits = vec![set(
|
||||
&appearance.map_or_else(
|
||||
@@ -333,6 +333,63 @@ mod tests {
|
||||
assert_eq!(family_selection(&snapshot, Some(Dark)), "catppuccin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_family_selection_clears_stale_scoped_gradients() {
|
||||
let source = r##"theme {
|
||||
family "ferese-blue"
|
||||
border { style solid; }
|
||||
light { family "gruvbox"; focus-ring { gradient { from "#3D7BE6"; to "#3D7BE6"; }; }; }
|
||||
dark {
|
||||
family "catppuccin"
|
||||
colors { accent "#3D7BE6"; }
|
||||
focus-ring { gradient { from "#3D7BE6"; to "#3D7BE6"; }; }
|
||||
background { path "/wallpaper.png"; }
|
||||
}
|
||||
}"##;
|
||||
for (id, _, _, _) in ferese_config::families::BUILTINS {
|
||||
let mut snapshot = Snapshot::parse(source.into()).unwrap();
|
||||
for edit in choose_family(id, None) {
|
||||
snapshot.edit(&edit).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
family_selection(&snapshot, Some(ferese_config::theme::Appearance::Light)),
|
||||
"gruvbox"
|
||||
);
|
||||
assert_eq!(
|
||||
family_selection(&snapshot, Some(ferese_config::theme::Appearance::Dark)),
|
||||
"catppuccin"
|
||||
);
|
||||
assert_eq!(snapshot.string("theme.dark.background.path", ""), "/wallpaper.png");
|
||||
assert_eq!(snapshot.string("theme.border.style", ""), "solid");
|
||||
for mode in ["light", "dark"] {
|
||||
assert!(snapshot.item(&format!("theme.{mode}.focus_ring")).is_none());
|
||||
snapshot.edit(&set("theme.mode", mode)).unwrap();
|
||||
let reference = Snapshot::parse(format!(
|
||||
"theme {{ family \"{id}\"; mode \"{mode}\"; border {{ style solid; }}; }}"
|
||||
))
|
||||
.unwrap();
|
||||
let resolve = |snapshot: &Snapshot| {
|
||||
ferese_config::theme::resolve(
|
||||
&snapshot.doc,
|
||||
std::path::Path::new("/config"),
|
||||
"2026-10-04T12:00:00Z".parse().unwrap(),
|
||||
|_| unreachable!(),
|
||||
)
|
||||
.unwrap()
|
||||
.theme
|
||||
};
|
||||
let actual = resolve(&snapshot);
|
||||
let expected = resolve(&reference);
|
||||
assert_eq!(actual.tokens.focus_ring, expected.tokens.focus_ring, "{id}/{mode}");
|
||||
assert_eq!(
|
||||
Palette::from_resolved(&actual).accent_gradient,
|
||||
Palette::from_resolved(&expected).accent_gradient
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_first_split_starts_with_both_variants_of_the_single_family() {
|
||||
use ferese_config::theme::Appearance::{Dark, Light};
|
||||
|
||||
@@ -60,18 +60,21 @@ struct Preview {
|
||||
|
||||
impl Preview {
|
||||
fn request(&mut self, path: &str) -> bool {
|
||||
if path.is_empty() {
|
||||
self.path.clear();
|
||||
self.requested = None;
|
||||
self.handle = None;
|
||||
self.error = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.requested.is_some() || self.path == path {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.handle = None;
|
||||
// Keep the last decoded image while its replacement loads. A stale
|
||||
// completion can then also return to that image without decoding again.
|
||||
self.error = None;
|
||||
if path.is_empty() {
|
||||
self.path.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
self.path.clear();
|
||||
self.requested = Some(path.to_owned());
|
||||
true
|
||||
}
|
||||
@@ -317,25 +320,26 @@ impl App {
|
||||
let palette = visuals::Palette::from_resolved(&self.resolved.presented);
|
||||
let preview = &self.wallpaper.active;
|
||||
let mut body = column([]).spacing(8).push(self.label("Current wallpaper", 13.));
|
||||
if let Some(handle) = &preview.handle {
|
||||
body = body.push(
|
||||
widget::container(
|
||||
widget::image(handle.clone())
|
||||
.border_radius(palette.radius.min(10.))
|
||||
.shape(cosmic::iced::border::Shape::Continuous)
|
||||
.width(Length::Shrink)
|
||||
.height(180)
|
||||
.content_fit(cosmic::iced::ContentFit::Contain),
|
||||
)
|
||||
.center_x(Length::Fill),
|
||||
);
|
||||
let current: Element<'_, Message> = if let Some(handle) = &preview.handle {
|
||||
widget::image(handle.clone())
|
||||
.border_radius(palette.radius.min(10.))
|
||||
.shape(cosmic::iced::border::Shape::Continuous)
|
||||
.width(Length::Shrink)
|
||||
.height(180)
|
||||
.content_fit(cosmic::iced::ContentFit::Contain)
|
||||
.into()
|
||||
} else {
|
||||
body = body.push(self.note(if preview.requested.is_some() {
|
||||
"Loading wallpaper preview…"
|
||||
} else {
|
||||
preview.error.as_deref().unwrap_or("No wallpaper image selected.")
|
||||
}));
|
||||
}
|
||||
self.wallpaper_placeholder(
|
||||
if preview.requested.is_some() {
|
||||
"Loading preview…"
|
||||
} else {
|
||||
preview.error.as_deref().unwrap_or("No wallpaper image selected.")
|
||||
},
|
||||
palette,
|
||||
180.,
|
||||
)
|
||||
};
|
||||
body = body.push(widget::container(current).height(180).center_x(Length::Fill));
|
||||
|
||||
body = body.push(
|
||||
row([])
|
||||
@@ -374,10 +378,7 @@ impl App {
|
||||
.content_fit(cosmic::iced::ContentFit::Cover)
|
||||
.into()
|
||||
} else {
|
||||
widget::container(self.note(preview.error.as_deref().unwrap_or("Loading…")))
|
||||
.width(Length::Fill)
|
||||
.height(110)
|
||||
.into()
|
||||
self.wallpaper_placeholder(preview.error.as_deref().unwrap_or("Loading preview…"), palette, 110.)
|
||||
};
|
||||
let label_color = if preview.handle.is_none() {
|
||||
palette.text
|
||||
@@ -421,12 +422,104 @@ impl App {
|
||||
.push(defaults)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn wallpaper_placeholder<'a>(
|
||||
&'a self,
|
||||
message: &'a str,
|
||||
palette: visuals::Palette,
|
||||
height: f32,
|
||||
) -> Element<'a, Message> {
|
||||
widget::container(
|
||||
column([])
|
||||
.spacing(8)
|
||||
.align_x(Alignment::Center)
|
||||
.push(visuals::action_icon(
|
||||
"M3 4h18v16H3z M3 15l5-5 5 5 3-3 5 5 M15 8h.01",
|
||||
palette.muted,
|
||||
))
|
||||
.push(self.note(message)),
|
||||
)
|
||||
.padding(16)
|
||||
.width(Length::Fill)
|
||||
.height(height)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(height)
|
||||
.class(visuals::surface(palette.card, palette.radius.min(10.)))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn preview_slots_keep_their_size_while_loading_and_on_failure() {
|
||||
use cosmic::Application;
|
||||
use cosmic::iced::advanced::renderer::Headless;
|
||||
use cosmic::iced::advanced::{layout, widget::Tree};
|
||||
use cosmic::iced::{Font, Pixels, Size};
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let renderer = runtime
|
||||
.block_on(<cosmic::Renderer as Headless>::new(
|
||||
Font::default(),
|
||||
Pixels(14.),
|
||||
Some("tiny-skia"),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
for width in [420., 760.] {
|
||||
let mut app = App::init(
|
||||
cosmic::app::Core::default(),
|
||||
("/preview/config.kdl".into(), Snapshot::parse(String::new()), None),
|
||||
)
|
||||
.0;
|
||||
let dimensions = |app: &App| {
|
||||
let mut view = app.wallpaper_controls();
|
||||
let mut tree = Tree::new(view.as_widget());
|
||||
view.as_widget_mut()
|
||||
.layout(
|
||||
&mut tree,
|
||||
&renderer,
|
||||
&layout::Limits::new(Size::ZERO, Size::new(width, f32::INFINITY)),
|
||||
)
|
||||
.size()
|
||||
};
|
||||
let initial = dimensions(&app);
|
||||
assert!(app.wallpaper.active.request("/active.png"));
|
||||
|
||||
for preview in &mut app.wallpaper.defaults {
|
||||
assert!(preview.request("/default.png"));
|
||||
}
|
||||
|
||||
assert_eq!(dimensions(&app), initial);
|
||||
let handle = widget::image::Handle::from_rgba(2, 1, vec![255; 8]);
|
||||
app.wallpaper
|
||||
.active
|
||||
.finish("/active.png".into(), "/active.png", Ok(handle.clone()));
|
||||
|
||||
for index in 0..app.wallpaper.defaults.len() {
|
||||
app.wallpaper.defaults[index].finish("/default.png".into(), "/default.png", Ok(handle.clone()));
|
||||
assert_eq!(dimensions(&app), initial);
|
||||
}
|
||||
|
||||
assert!(app.wallpaper.active.request("/missing.png"));
|
||||
assert_eq!(dimensions(&app), initial);
|
||||
app.wallpaper.active.finish(
|
||||
"/missing.png".into(),
|
||||
"/missing.png",
|
||||
Err("Could not load this image.".into()),
|
||||
);
|
||||
assert_eq!(dimensions(&app), initial);
|
||||
assert!(!app.wallpaper.active.request(""));
|
||||
assert_eq!(dimensions(&app), initial);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "release UI preview; requires headless renderer backends"]
|
||||
fn render_preview_pages_on_both_backends() {
|
||||
@@ -564,14 +657,47 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returning_to_a_previous_path_during_a_load_restarts_its_preview() {
|
||||
fn returning_to_a_previous_path_during_a_load_reuses_its_preview() {
|
||||
let mut preview = Preview::default();
|
||||
let image = widget::image::Handle::from_rgba(1, 1, vec![0, 0, 0, 255]);
|
||||
assert!(preview.request("/first.png"));
|
||||
preview.finish("/first.png".into(), "/first.png", Ok(image.clone()));
|
||||
assert!(preview.request("/second.png"));
|
||||
preview.finish("/second.png".into(), "/first.png", Ok(image));
|
||||
assert!(!preview.request("/first.png"));
|
||||
assert!(preview.handle.is_some());
|
||||
assert_eq!(preview.path, "/first.png");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacement_keeps_the_previous_image_until_it_is_ready() {
|
||||
let mut preview = Preview::default();
|
||||
let first = widget::image::Handle::from_rgba(1, 1, vec![0, 0, 0, 255]);
|
||||
let second = widget::image::Handle::from_rgba(1, 1, vec![255, 255, 255, 255]);
|
||||
assert!(preview.request("/first.png"));
|
||||
preview.finish("/first.png".into(), "/first.png", Ok(first.clone()));
|
||||
assert!(preview.request("/second.png"));
|
||||
assert_eq!(preview.handle.as_ref().unwrap().id(), first.id());
|
||||
assert_eq!(preview.path, "/first.png");
|
||||
assert!(!preview.request("/second.png"));
|
||||
preview.finish("/second.png".into(), "/second.png", Ok(second.clone()));
|
||||
assert_eq!(preview.handle.as_ref().unwrap().id(), second.id());
|
||||
assert_eq!(preview.path, "/second.png");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clearing_wallpaper_during_a_load_does_not_restore_the_old_image() {
|
||||
let mut preview = Preview::default();
|
||||
let image = widget::image::Handle::from_rgba(1, 1, vec![0, 0, 0, 255]);
|
||||
assert!(preview.request("/first.png"));
|
||||
preview.finish("/first.png".into(), "/first.png", Ok(image.clone()));
|
||||
assert!(preview.request("/second.png"));
|
||||
assert!(!preview.request(""));
|
||||
assert!(preview.handle.is_none());
|
||||
assert!(preview.requested.is_none());
|
||||
preview.finish("/second.png".into(), "", Ok(image));
|
||||
assert!(preview.handle.is_none());
|
||||
assert!(preview.path.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -16,6 +16,7 @@ mod note_store;
|
||||
mod notification_ui;
|
||||
mod notifications;
|
||||
mod recording;
|
||||
mod renderer;
|
||||
mod status;
|
||||
mod status_ui;
|
||||
mod system_modal;
|
||||
@@ -109,13 +110,7 @@ impl From<ShellTheme> for BarMetrics {
|
||||
}
|
||||
|
||||
fn main() -> cosmic::iced::Result {
|
||||
// Small shell surfaces do not need a second GPU device and shader setup.
|
||||
// Keep wgpu as a fallback and honor an explicit renderer preference.
|
||||
if std::env::var_os("ICED_BACKEND").is_none() {
|
||||
// SAFETY: this is the process entry point, before any worker or
|
||||
// toolkit threads are started.
|
||||
unsafe { std::env::set_var("ICED_BACKEND", "tiny-skia,wgpu") };
|
||||
}
|
||||
renderer::configure_shell();
|
||||
|
||||
cosmic::iced::advanced::graphics::text::font_system()
|
||||
.write()
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::process::Command;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static APP_BACKEND: OnceLock<Option<OsString>> = OnceLock::new();
|
||||
|
||||
/// Called at process entry, before the toolkit or workers start threads.
|
||||
pub(crate) fn configure_shell() {
|
||||
let backend = std::env::var_os("ICED_BACKEND");
|
||||
let use_shell_default = backend.is_none();
|
||||
APP_BACKEND.set(backend).expect("renderer configured once");
|
||||
|
||||
if use_shell_default {
|
||||
// Small shell surfaces do not need a second GPU device and shader setup.
|
||||
// SAFETY: main calls this before starting any threads.
|
||||
unsafe { std::env::set_var("ICED_BACKEND", "tiny-skia,wgpu") };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn configure_app(command: &mut Command) {
|
||||
if let Some(backend) = APP_BACKEND.get() {
|
||||
apply_app_backend(command, backend.as_deref());
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_app_backend(command: &mut Command, backend: Option<&OsStr>) {
|
||||
if let Some(backend) = backend {
|
||||
command.env("ICED_BACKEND", backend);
|
||||
} else {
|
||||
// The shell's automatic software preference must not leak into Settings.
|
||||
command.env_remove("ICED_BACKEND");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn settings_does_not_inherit_the_shells_automatic_software_renderer() {
|
||||
let mut command = Command::new("ferese-settings");
|
||||
command.env("ICED_BACKEND", "tiny-skia,wgpu");
|
||||
apply_app_backend(&mut command, None);
|
||||
assert_eq!(
|
||||
command.get_envs().find(|(key, _)| *key == "ICED_BACKEND").unwrap().1,
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_preserves_explicit_renderer_preferences() {
|
||||
for backend in ["tiny-skia", "wgpu", "wgpu,tiny-skia"] {
|
||||
let mut command = Command::new("ferese-settings");
|
||||
apply_app_backend(&mut command, Some(OsStr::new(backend)));
|
||||
assert_eq!(
|
||||
command.get_envs().find(|(key, _)| *key == "ICED_BACKEND").unwrap().1,
|
||||
Some(OsStr::new(backend))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -632,6 +632,7 @@ fn execute(action: &Action, settings: Option<&[String]>) -> Result<(), String> {
|
||||
command.env("WAYLAND_DISPLAY", display);
|
||||
}
|
||||
command.env_remove("FERESE_PUBLIC_WAYLAND_DISPLAY");
|
||||
crate::renderer::configure_app(&mut command);
|
||||
let mut child = command.spawn().map_err(|e| e.to_string())?;
|
||||
thread::spawn(move || {
|
||||
let _ = child.wait();
|
||||
|
||||
Reference in new issue
Block a user