From de2c9c5a73b1a3ed9ef378fa0f541a9e9a6b6acf Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 01:27:33 +0100 Subject: [PATCH 01/10] okayish solution should be improved at some point but for now it works well enough. --- desktop/src/app.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 2b8f7df42d..10ac6db039 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -341,8 +341,10 @@ impl App { NodeGraphExecutionResult::NotRun => {} }, AppEvent::UiUpdate(texture) => { + let width = texture.width(); + let height = texture.height(); if let Some(graphics_state) = self.graphics_state.as_mut() { - graphics_state.resize(texture.width(), texture.height()); + graphics_state.resize(width, height); graphics_state.bind_ui_texture(texture); let elapsed = self.last_ui_update.elapsed().as_secs_f32(); self.last_ui_update = Instant::now(); @@ -351,6 +353,20 @@ impl App { } } if let Some(window) = &self.window { + // Workaround for missing resize events on mac + // TODO: Find a cleaner solution + #[cfg(target_os = "macos")] + { + let surface_size = window.surface_size(); + if width != surface_size.width || height != surface_size.height { + let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size { + width: surface_size.width as usize, + height: surface_size.height as usize, + }); + self.cef_context.notify_view_info_changed(); + } + } + window.request_redraw(); } } From 0656d5e4436f6f5c0a21986e14bd3d2021971ada Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 01:27:33 +0100 Subject: [PATCH 02/10] do leftover renames --- desktop/bundle/src/win.rs | 2 +- desktop/src/cli.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/bundle/src/win.rs b/desktop/bundle/src/win.rs index d07c3ef9bb..d3e159e410 100644 --- a/desktop/bundle/src/win.rs +++ b/desktop/bundle/src/win.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use crate::common::*; const PACKAGE: &str = "graphite-desktop-platform-win"; -const EXECUTABLE: &str = "graphite-editor.exe"; +const EXECUTABLE: &str = "graphite.exe"; pub fn main() -> Result<(), Box> { let app_bin = build_bin(PACKAGE, None)?; diff --git a/desktop/src/cli.rs b/desktop/src/cli.rs index 4e54bd2477..3af8e10c4a 100644 --- a/desktop/src/cli.rs +++ b/desktop/src/cli.rs @@ -1,5 +1,5 @@ #[derive(clap::Parser)] -#[clap(name = "graphite-editor", version)] +#[clap(name = "graphite", version)] pub struct Cli { #[arg(help = "Files to open on startup")] pub files: Vec, From e6a3635db962e79f5ae88b508db429b0c3a27191 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 01:27:33 +0100 Subject: [PATCH 03/10] better solution --- desktop/src/app.rs | 97 +++++++++---------- desktop/src/cef.rs | 10 +- desktop/src/consts.rs | 1 + desktop/src/render.rs | 4 +- .../render/{graphics_state.rs => state.rs} | 22 ++++- desktop/wrapper/src/utils.rs | 14 ++- 6 files changed, 80 insertions(+), 68 deletions(-) rename desktop/src/render/{graphics_state.rs => state.rs} (95%) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 10ac6db039..7d684932e0 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -18,22 +18,22 @@ use crate::cef; use crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS; use crate::event::{AppEvent, AppEventScheduler}; use crate::persist::PersistentData; -use crate::render::GraphicsState; +use crate::render::{RenderError, RenderState}; use crate::window::Window; use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform}; use crate::wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages}; pub(crate) struct App { - cef_context: Box, + render_state: Option, + wgpu_context: WgpuContext, window: Option, window_scale: f64, - cef_schedule: Option, - cef_view_info_sender: Sender, - graphics_state: Option, - wgpu_context: WgpuContext, app_event_receiver: Receiver, app_event_scheduler: AppEventScheduler, desktop_wrapper: DesktopWrapper, + cef_context: Box, + cef_schedule: Option, + cef_view_info_sender: Sender, last_ui_update: Instant, avg_frame_time: f32, start_render_sender: SyncSender<()>, @@ -77,17 +77,17 @@ impl App { persistent_data.load_from_disk(); Self { - cef_context, - window: None, - window_scale: 1.0, - cef_schedule: Some(Instant::now()), - graphics_state: None, - cef_view_info_sender, + render_state: None, wgpu_context, + window: None, + window_scale: 1., app_event_receiver, app_event_scheduler, desktop_wrapper: DesktopWrapper::new(), last_ui_update: Instant::now(), + cef_context, + cef_schedule: Some(Instant::now()), + cef_view_info_sender, avg_frame_time: 0., start_render_sender, web_communication_initialized: false, @@ -97,6 +97,17 @@ impl App { } } + fn resize_window(&mut self, width: u32, height: u32) { + let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size { width, height }); + self.cef_context.notify_view_info_changed(); + if let Some(render_state) = &mut self.render_state { + render_state.resize(width, height); + } + if let Some(window) = &self.window { + window.request_redraw(); + } + } + fn handle_desktop_frontend_message(&mut self, message: DesktopFrontendMessage, responses: &mut Vec) { match message { DesktopFrontendMessage::ToWeb(messages) => { @@ -162,23 +173,23 @@ impl App { }); } DesktopFrontendMessage::UpdateViewportPhysicalBounds { x, y, width, height } => { - if let Some(graphics_state) = &mut self.graphics_state + if let Some(render_state) = &mut self.render_state && let Some(window) = &self.window { let window_size = window.surface_size(); let viewport_offset_x = x / window_size.width as f64; let viewport_offset_y = y / window_size.height as f64; - graphics_state.set_viewport_offset([viewport_offset_x as f32, viewport_offset_y as f32]); + render_state.set_viewport_offset([viewport_offset_x as f32, viewport_offset_y as f32]); let viewport_scale_x = if width != 0.0 { window_size.width as f64 / width } else { 1.0 }; let viewport_scale_y = if height != 0.0 { window_size.height as f64 / height } else { 1.0 }; - graphics_state.set_viewport_scale([viewport_scale_x as f32, viewport_scale_y as f32]); + render_state.set_viewport_scale([viewport_scale_x as f32, viewport_scale_y as f32]); } } DesktopFrontendMessage::UpdateOverlays(scene) => { - if let Some(graphics_state) = &mut self.graphics_state { - graphics_state.set_overlays_scene(scene); + if let Some(render_state) = &mut self.render_state { + render_state.set_overlays_scene(scene); } } DesktopFrontendMessage::PersistenceWriteDocument { id, document } => { @@ -331,21 +342,18 @@ impl App { NodeGraphExecutionResult::HasRun(texture) => { self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::PollNodeGraphEvaluation); if let Some(texture) = texture - && let Some(graphics_state) = self.graphics_state.as_mut() + && let Some(render_state) = self.render_state.as_mut() && let Some(window) = self.window.as_ref() { - graphics_state.bind_viewport_texture(texture); + render_state.bind_viewport_texture(texture); window.request_redraw(); } } NodeGraphExecutionResult::NotRun => {} }, AppEvent::UiUpdate(texture) => { - let width = texture.width(); - let height = texture.height(); - if let Some(graphics_state) = self.graphics_state.as_mut() { - graphics_state.resize(width, height); - graphics_state.bind_ui_texture(texture); + if let Some(render_state) = self.render_state.as_mut() { + render_state.bind_ui_texture(texture); let elapsed = self.last_ui_update.elapsed().as_secs_f32(); self.last_ui_update = Instant::now(); if elapsed < 0.5 { @@ -353,20 +361,6 @@ impl App { } } if let Some(window) = &self.window { - // Workaround for missing resize events on mac - // TODO: Find a cleaner solution - #[cfg(target_os = "macos")] - { - let surface_size = window.surface_size(); - if width != surface_size.width || height != surface_size.height { - let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size { - width: surface_size.width as usize, - height: surface_size.height as usize, - }); - self.cef_context.notify_view_info_changed(); - } - } - window.request_redraw(); } } @@ -405,9 +399,8 @@ impl ApplicationHandler for App { self.window = Some(window); - let graphics_state = GraphicsState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone()); - - self.graphics_state = Some(graphics_state); + let render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone()); + self.render_state = Some(render_state); self.desktop_wrapper.init(self.wgpu_context.clone()); @@ -434,11 +427,8 @@ impl ApplicationHandler for App { self.app_event_scheduler.schedule(AppEvent::CloseWindow); } WindowEvent::SurfaceResized(PhysicalSize { width, height }) => { - let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size { - width: width as usize, - height: height as usize, - }); - self.cef_context.notify_view_info_changed(); + self.resize_window(width, height); + if let Some(window) = &self.window { let maximized = window.is_maximized(); self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(DesktopWrapperMessage::UpdateMaximized { maximized })); @@ -450,18 +440,21 @@ impl ApplicationHandler for App { self.cef_context.notify_view_info_changed(); } WindowEvent::RedrawRequested => { - let Some(ref mut graphics_state) = self.graphics_state else { return }; - // Only rerender once we have a new UI texture to display + let Some(render_state) = &mut self.render_state else { return }; if let Some(window) = &self.window { - match graphics_state.render(window) { + match render_state.render(window) { Ok(_) => {} - Err(wgpu::SurfaceError::Lost) => { + Err(RenderError::OutdatedUITextureError) => { + self.cef_context.notify_view_info_changed(); + } + Err(RenderError::SurfaceError(wgpu::SurfaceError::Lost)) => { tracing::warn!("lost surface"); } - Err(wgpu::SurfaceError::OutOfMemory) => { + Err(RenderError::SurfaceError(wgpu::SurfaceError::OutOfMemory)) => { + tracing::error!("GPU out of memory"); event_loop.exit(); } - Err(e) => tracing::error!("{:?}", e), + Err(RenderError::SurfaceError(e)) => tracing::error!("Render error: {:?}", e), } let _ = self.start_render_sender.try_send(()); } diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 1b5383c4fb..c28dad60e7 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -55,8 +55,8 @@ pub(crate) trait CefEventHandler: Send + Sync + 'static { #[derive(Clone, Copy)] pub(crate) struct ViewInfo { - width: usize, - height: usize, + width: u32, + height: u32, scale: f64, } impl ViewInfo { @@ -78,10 +78,10 @@ impl ViewInfo { pub(crate) fn zoom(&self) -> f64 { self.scale.ln() / 1.2_f64.ln() } - pub(crate) fn width(&self) -> usize { + pub(crate) fn width(&self) -> u32 { self.width } - pub(crate) fn height(&self) -> usize { + pub(crate) fn height(&self) -> u32 { self.height } } @@ -92,7 +92,7 @@ impl Default for ViewInfo { } pub(crate) enum ViewInfoUpdate { - Size { width: usize, height: usize }, + Size { width: u32, height: u32 }, Scale(f64), } diff --git a/desktop/src/consts.rs b/desktop/src/consts.rs index 28879574d1..03154e4206 100644 --- a/desktop/src/consts.rs +++ b/desktop/src/consts.rs @@ -1,4 +1,5 @@ pub(crate) const APP_NAME: &str = "Graphite"; +#[cfg(target_os = "linux")] pub(crate) const APP_ID: &str = "rs.graphite.Graphite"; pub(crate) const APP_DIRECTORY_NAME: &str = "graphite"; diff --git a/desktop/src/render.rs b/desktop/src/render.rs index df03381f08..e5d57982dc 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -1,5 +1,5 @@ mod frame_buffer_ref; pub(crate) use frame_buffer_ref::FrameBufferRef; -mod graphics_state; -pub(crate) use graphics_state::GraphicsState; +mod state; +pub(crate) use state::{RenderError, RenderState}; diff --git a/desktop/src/render/graphics_state.rs b/desktop/src/render/state.rs similarity index 95% rename from desktop/src/render/graphics_state.rs rename to desktop/src/render/state.rs index 476e592650..9852817ced 100644 --- a/desktop/src/render/graphics_state.rs +++ b/desktop/src/render/state.rs @@ -4,7 +4,7 @@ use crate::wrapper::{Color, WgpuContext, WgpuExecutor}; #[derive(derivative::Derivative)] #[derivative(Debug)] -pub(crate) struct GraphicsState { +pub(crate) struct RenderState { surface: wgpu::Surface<'static>, context: WgpuContext, executor: WgpuExecutor, @@ -22,7 +22,7 @@ pub(crate) struct GraphicsState { overlays_scene: Option, } -impl GraphicsState { +impl RenderState { pub(crate) fn new(window: &Window, context: WgpuContext) -> Self { let size = window.surface_size(); let surface = window.create_surface(context.instance.clone()); @@ -230,12 +230,15 @@ impl GraphicsState { self.bind_overlays_texture(texture); } - pub(crate) fn render(&mut self, window: &Window) -> Result<(), wgpu::SurfaceError> { + pub(crate) fn render(&mut self, window: &Window) -> Result<(), RenderError> { if let Some(scene) = self.overlays_scene.take() { self.render_overlays(scene); } - let output = self.surface.get_current_texture()?; + let output = self.surface.get_current_texture().map_err(|e| RenderError::SurfaceError(e))?; + let output_width = output.texture.width(); + let output_height = output.texture.height(); + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") }); @@ -277,6 +280,12 @@ impl GraphicsState { window.pre_present_notify(); output.present(); + if let Some(ui_texture) = &self.ui_texture + && (output_width != ui_texture.width() || output_height != ui_texture.height()) + { + return Err(RenderError::OutdatedUITextureError); + } + Ok(()) } @@ -312,6 +321,11 @@ impl GraphicsState { } } +pub(crate) enum RenderError { + OutdatedUITextureError, + SurfaceError(wgpu::SurfaceError), +} + #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct Constants { diff --git a/desktop/wrapper/src/utils.rs b/desktop/wrapper/src/utils.rs index 73a49960a1..5becb892ae 100644 --- a/desktop/wrapper/src/utils.rs +++ b/desktop/wrapper/src/utils.rs @@ -3,7 +3,7 @@ pub(crate) mod menu { use base64::engine::Engine; use base64::engine::general_purpose::STANDARD as BASE64; - use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKey, LabeledShortcut}; + use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKeyOrMouseMotion, LabeledShortcut}; use graphite_editor::messages::input_mapper::utility_types::misc::ActionShortcut; use graphite_editor::messages::layout::LayoutMessage; use graphite_editor::messages::tool::tool_messages::tool_prelude::{Layout, LayoutGroup, LayoutTarget, MenuListEntry, Widget, WidgetId}; @@ -68,9 +68,9 @@ pub(crate) mod menu { value, label, icon, - shortcut_keys, - children, disabled, + tooltip_shortcut, + children, .. }: &MenuListEntry = entry; path.push(value.clone()); @@ -83,7 +83,7 @@ pub(crate) mod menu { return MenuItem::SubMenu { id, text, enabled, items }; } - let shortcut = match shortcut_keys { + let shortcut = match tooltip_shortcut { Some(ActionShortcut::Shortcut(LabeledShortcut(shortcut))) => convert_labeled_keys_to_shortcut(shortcut), _ => None, }; @@ -126,10 +126,14 @@ pub(crate) mod menu { items } - fn convert_labeled_keys_to_shortcut(labeled_keys: &Vec) -> Option { + fn convert_labeled_keys_to_shortcut(labeled_keys: &Vec) -> Option { let mut key: Option = None; let mut modifiers = Modifiers::default(); for labeled_key in labeled_keys { + let LabeledKeyOrMouseMotion::Key(labeled_key) = labeled_key else { + // Return None for shortcuts that include mouse motion because we can't show them in native menu + return None; + }; match labeled_key.key() { Key::Shift => modifiers |= Modifiers::SHIFT, Key::Control => modifiers |= Modifiers::CONTROL, From 50d0bbf0d33b2f803555a102aa2a767077529f83 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 01:27:33 +0100 Subject: [PATCH 04/10] less weird resize frames --- desktop/src/render/composite_shader.wgsl | 11 ++++++--- desktop/src/render/state.rs | 30 +++++++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/desktop/src/render/composite_shader.wgsl b/desktop/src/render/composite_shader.wgsl index 20ee43f1b2..10b65d76ba 100644 --- a/desktop/src/render/composite_shader.wgsl +++ b/desktop/src/render/composite_shader.wgsl @@ -43,14 +43,19 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return ui_linear; } + // UI texture is premultiplied, we need to unpremultiply before blending + let ui_srgb = linear_to_srgb(unpremultiply(ui_linear)); + let viewport_coordinate = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale; + if (viewport_coordinate.x < 0.0 || viewport_coordinate.x > 1.0 || + viewport_coordinate.y < 0.0 || viewport_coordinate.y > 1.0) { + return srgb_to_linear(blend(ui_srgb, vec4(0.1289, 0.1289, 0.1289, 1.0))); + } + let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate); let viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate); - // UI texture is premultiplied, we need to unpremultiply before blending - let ui_srgb = linear_to_srgb(unpremultiply(ui_linear)); - if (overlay_srgb.a < 0.001) { if (ui_srgb.a < 0.001) { return srgb_to_linear(viewport_srgb); diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 9852817ced..8889d2db38 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -12,6 +12,8 @@ pub(crate) struct RenderState { render_pipeline: wgpu::RenderPipeline, transparent_texture: wgpu::Texture, sampler: wgpu::Sampler, + desired_width: u32, + desired_height: u32, viewport_scale: [f32; 2], viewport_offset: [f32; 2], viewport_texture: Option, @@ -171,6 +173,8 @@ impl RenderState { render_pipeline, transparent_texture, sampler, + desired_width: size.width, + desired_height: size.height, viewport_scale: [1.0, 1.0], viewport_offset: [0.0, 0.0], viewport_texture: None, @@ -182,11 +186,8 @@ impl RenderState { } pub(crate) fn resize(&mut self, width: u32, height: u32) { - if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { - self.config.width = width; - self.config.height = height; - self.surface.configure(&self.context.device, &self.config); - } + self.desired_width = width; + self.desired_height = height; } pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: wgpu::Texture) { @@ -200,8 +201,17 @@ impl RenderState { } pub(crate) fn bind_ui_texture(&mut self, bind_ui_texture: wgpu::Texture) { + let width = bind_ui_texture.width(); + let height = bind_ui_texture.height(); + self.ui_texture = Some(bind_ui_texture); self.update_bindgroup(); + + if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { + self.config.width = width; + self.config.height = height; + self.surface.configure(&self.context.device, &self.config); + } } pub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) { @@ -235,9 +245,7 @@ impl RenderState { self.render_overlays(scene); } - let output = self.surface.get_current_texture().map_err(|e| RenderError::SurfaceError(e))?; - let output_width = output.texture.width(); - let output_height = output.texture.height(); + let output = self.surface.get_current_texture().map_err(RenderError::SurfaceError)?; let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); @@ -245,7 +253,7 @@ impl RenderState { { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), + label: Some("Graphite Composition Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, @@ -271,7 +279,7 @@ impl RenderState { ); if let Some(bind_group) = &self.bind_group { render_pass.set_bind_group(0, bind_group, &[]); - render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle + render_pass.draw(0..3, 0..1); // Draw 3 vertices for fullscreen triangle } else { tracing::warn!("No bind group available - showing clear color only"); } @@ -281,7 +289,7 @@ impl RenderState { output.present(); if let Some(ui_texture) = &self.ui_texture - && (output_width != ui_texture.width() || output_height != ui_texture.height()) + && (self.desired_width != ui_texture.width() || self.desired_height != ui_texture.height()) { return Err(RenderError::OutdatedUITextureError); } From 615f618528cf1ecb0c494a1fdf9fb0b40897bc1e Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 01:27:33 +0100 Subject: [PATCH 05/10] move surface reconfiguration --- desktop/src/render/state.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 8889d2db38..1a538db906 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -188,6 +188,12 @@ impl RenderState { pub(crate) fn resize(&mut self, width: u32, height: u32) { self.desired_width = width; self.desired_height = height; + + if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { + self.config.width = width; + self.config.height = height; + self.surface.configure(&self.context.device, &self.config); + } } pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: wgpu::Texture) { @@ -201,17 +207,8 @@ impl RenderState { } pub(crate) fn bind_ui_texture(&mut self, bind_ui_texture: wgpu::Texture) { - let width = bind_ui_texture.width(); - let height = bind_ui_texture.height(); - self.ui_texture = Some(bind_ui_texture); self.update_bindgroup(); - - if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { - self.config.width = width; - self.config.height = height; - self.surface.configure(&self.context.device, &self.config); - } } pub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) { From a9f76d3e2970bd4812122b20d86a311484656103 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 01:59:21 +0100 Subject: [PATCH 06/10] fix recent desktop mac breakages --- desktop/wrapper/src/intercept_frontend_message.rs | 5 +---- desktop/wrapper/src/utils.rs | 2 +- editor/src/messages/layout/layout_message_handler.rs | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 24ee4c5b1b..5b049eac34 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -111,10 +111,7 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD dispatcher.respond(DesktopFrontendMessage::PersistenceLoadPreferences); } #[cfg(target_os = "macos")] - FrontendMessage::UpdateMenuBarLayout { - layout_target: graphite_editor::messages::tool::tool_messages::tool_prelude::LayoutTarget::MenuBar, - diff, - } => { + FrontendMessage::UpdateMenuBarLayout { diff } => { use graphite_editor::messages::tool::tool_messages::tool_prelude::{DiffUpdate, WidgetDiff}; match diff.as_slice() { [ diff --git a/desktop/wrapper/src/utils.rs b/desktop/wrapper/src/utils.rs index 5becb892ae..c62fe8f466 100644 --- a/desktop/wrapper/src/utils.rs +++ b/desktop/wrapper/src/utils.rs @@ -10,7 +10,7 @@ pub(crate) mod menu { use crate::messages::{EditorMessage, KeyCode, MenuItem, Modifiers, Shortcut}; - pub(crate) fn convert_menu_bar_layout_to_menu_items(layout: &Layout) -> Vec { + pub(crate) fn convert_menu_bar_layout_to_menu_items(Layout(layout): &Layout) -> Vec { let layout_group = match layout.as_slice() { [layout_group] => layout_group, _ => panic!("Menu bar layout is supposed to have exactly one layout group"), diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 0f4f75a6f2..7fe27b46c5 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -488,7 +488,7 @@ impl LayoutMessageHandler { if layout_target == LayoutTarget::MenuBar { widget_diffs = vec![WidgetDiff { widget_path: Vec::new(), - new_value: DiffUpdate::Layout(current.layout.clone()), + new_value: DiffUpdate::Layout(self.layouts[LayoutTarget::MenuBar as usize].clone()), }]; } From c036630352f9ce03a24ed974fcf0410532c3e056 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 12:57:12 +0100 Subject: [PATCH 07/10] better looking resize on mac --- desktop/src/app.rs | 3 +++ desktop/src/render/composite_shader.wgsl | 15 +++++++++------ desktop/src/render/state.rs | 22 ++++++++++++++++++---- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 7d684932e0..186ec7c821 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -442,6 +442,9 @@ impl ApplicationHandler for App { WindowEvent::RedrawRequested => { let Some(render_state) = &mut self.render_state else { return }; if let Some(window) = &self.window { + let size = window.surface_size(); + render_state.resize(size.width, size.height); + match render_state.render(window) { Ok(_) => {} Err(RenderError::OutdatedUITextureError) => { diff --git a/desktop/src/render/composite_shader.wgsl b/desktop/src/render/composite_shader.wgsl index 10b65d76ba..05ba673305 100644 --- a/desktop/src/render/composite_shader.wgsl +++ b/desktop/src/render/composite_shader.wgsl @@ -23,6 +23,8 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { struct Constants { viewport_scale: vec2, viewport_offset: vec2, + ui_scale: vec2, + background_color: vec4, }; var constants: Constants; @@ -38,7 +40,13 @@ var s_diffuse: sampler; @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let ui_linear = srgb_to_linear(textureSample(t_ui, s_diffuse, in.tex_coords)); + let ui_coordinate = in.tex_coords * constants.ui_scale; + if (ui_coordinate.x < 0.0 || ui_coordinate.x > 1.0 || + ui_coordinate.y < 0.0 || ui_coordinate.y > 1.0) { + return srgb_to_linear(vec4(0.1289, 0.1289, 0.1289, 1.0)); + } + + let ui_linear = srgb_to_linear(textureSample(t_ui, s_diffuse, ui_coordinate)); if (ui_linear.a >= 0.999) { return ui_linear; } @@ -48,11 +56,6 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let viewport_coordinate = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale; - if (viewport_coordinate.x < 0.0 || viewport_coordinate.x > 1.0 || - viewport_coordinate.y < 0.0 || viewport_coordinate.y > 1.0) { - return srgb_to_linear(blend(ui_srgb, vec4(0.1289, 0.1289, 0.1289, 1.0))); - } - let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate); let viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate); diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index 1a538db906..bf2248bcef 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -186,6 +186,10 @@ impl RenderState { } pub(crate) fn resize(&mut self, width: u32, height: u32) { + if width == self.desired_width && height == self.desired_height { + return; + } + self.desired_width = width; self.desired_height = height; @@ -238,6 +242,14 @@ impl RenderState { } pub(crate) fn render(&mut self, window: &Window) -> Result<(), RenderError> { + let ui_scale = if let Some(ui_texture) = &self.ui_texture + && (self.desired_width != ui_texture.width() || self.desired_height != ui_texture.height()) + { + Some([self.desired_width as f32 / ui_texture.width() as f32, self.desired_height as f32 / ui_texture.height() as f32]) + } else { + None + }; + if let Some(scene) = self.overlays_scene.take() { self.render_overlays(scene); } @@ -255,7 +267,7 @@ impl RenderState { view: &view, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }), + load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1. }), store: wgpu::StoreOp::Store, }, depth_slice: None, @@ -272,6 +284,8 @@ impl RenderState { bytemuck::bytes_of(&Constants { viewport_scale: self.viewport_scale, viewport_offset: self.viewport_offset, + ui_scale: ui_scale.unwrap_or([1., 1.]), + background_color: [255. / 33., 255. / 33., 255. / 33., 255. / 33.], // #212121 }), ); if let Some(bind_group) = &self.bind_group { @@ -285,9 +299,7 @@ impl RenderState { window.pre_present_notify(); output.present(); - if let Some(ui_texture) = &self.ui_texture - && (self.desired_width != ui_texture.width() || self.desired_height != ui_texture.height()) - { + if ui_scale.is_some() { return Err(RenderError::OutdatedUITextureError); } @@ -336,4 +348,6 @@ pub(crate) enum RenderError { struct Constants { viewport_scale: [f32; 2], viewport_offset: [f32; 2], + ui_scale: [f32; 2], + background_color: [f32; 4], } From 541fa061846cae28e4924706e4bd3d42735a9a0d Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 5 Dec 2025 20:25:38 +0000 Subject: [PATCH 08/10] fix background color --- desktop/src/render/composite_shader.wgsl | 6 +++++- desktop/src/render/state.rs | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/desktop/src/render/composite_shader.wgsl b/desktop/src/render/composite_shader.wgsl index 05ba673305..f49144be74 100644 --- a/desktop/src/render/composite_shader.wgsl +++ b/desktop/src/render/composite_shader.wgsl @@ -43,7 +43,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let ui_coordinate = in.tex_coords * constants.ui_scale; if (ui_coordinate.x < 0.0 || ui_coordinate.x > 1.0 || ui_coordinate.y < 0.0 || ui_coordinate.y > 1.0) { - return srgb_to_linear(vec4(0.1289, 0.1289, 0.1289, 1.0)); + return srgb_to_linear(constants.background_color); } let ui_linear = srgb_to_linear(textureSample(t_ui, s_diffuse, ui_coordinate)); @@ -55,6 +55,10 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let ui_srgb = linear_to_srgb(unpremultiply(ui_linear)); let viewport_coordinate = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale; + if (viewport_coordinate.x < 0.0 || viewport_coordinate.x > 1.0 || + viewport_coordinate.y < 0.0 || viewport_coordinate.y > 1.0) { + return srgb_to_linear(constants.background_color); + } let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate); let viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate); diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index bf2248bcef..c4c9ee21f7 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -285,7 +285,8 @@ impl RenderState { viewport_scale: self.viewport_scale, viewport_offset: self.viewport_offset, ui_scale: ui_scale.unwrap_or([1., 1.]), - background_color: [255. / 33., 255. / 33., 255. / 33., 255. / 33.], // #212121 + _pad: [0., 0.], + background_color: [33. / 255., 33. / 255., 33. / 255., 1.], // #212121 }), ); if let Some(bind_group) = &self.bind_group { @@ -349,5 +350,6 @@ struct Constants { viewport_scale: [f32; 2], viewport_offset: [f32; 2], ui_scale: [f32; 2], + _pad: [f32; 2], background_color: [f32; 4], } From cc0cc9644bd29859e40c4c6fa108607b8dc0797c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 5 Dec 2025 16:18:19 -0800 Subject: [PATCH 09/10] Fix blank screen on window initialization --- desktop/src/app.rs | 6 ++++++ desktop/src/render/state.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 186ec7c821..bb00b7dcae 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -395,6 +395,12 @@ impl ApplicationHandler for App { self.window_scale = window.scale_factor(); let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Scale(self.window_scale)); + + // Ensures the CEF texture does not remain at 1x1 pixels until the window is resized by the user + // Affects only some Mac devices (issue found on 2023 M2 Mac Mini). + let PhysicalSize { width, height } = window.surface_size(); + let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size { width, height }); + self.cef_context.notify_view_info_changed(); self.window = Some(window); diff --git a/desktop/src/render/state.rs b/desktop/src/render/state.rs index c4c9ee21f7..b03e4659ef 100644 --- a/desktop/src/render/state.rs +++ b/desktop/src/render/state.rs @@ -286,7 +286,7 @@ impl RenderState { viewport_offset: self.viewport_offset, ui_scale: ui_scale.unwrap_or([1., 1.]), _pad: [0., 0.], - background_color: [33. / 255., 33. / 255., 33. / 255., 1.], // #212121 + background_color: [0x22 as f32 / 0xff as f32, 0x22 as f32 / 0xff as f32, 0x22 as f32 / 0xff as f32, 1.], // #222222 }), ); if let Some(bind_group) = &self.bind_group { From 993c3fd4858a21141513a09ecc04dbc155d1bfad Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Sat, 6 Dec 2025 14:19:01 +0000 Subject: [PATCH 10/10] cleanup --- desktop/src/app.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index bb00b7dcae..96c877f82f 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -97,17 +97,6 @@ impl App { } } - fn resize_window(&mut self, width: u32, height: u32) { - let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size { width, height }); - self.cef_context.notify_view_info_changed(); - if let Some(render_state) = &mut self.render_state { - render_state.resize(width, height); - } - if let Some(window) = &self.window { - window.request_redraw(); - } - } - fn handle_desktop_frontend_message(&mut self, message: DesktopFrontendMessage, responses: &mut Vec) { match message { DesktopFrontendMessage::ToWeb(messages) => { @@ -433,11 +422,18 @@ impl ApplicationHandler for App { self.app_event_scheduler.schedule(AppEvent::CloseWindow); } WindowEvent::SurfaceResized(PhysicalSize { width, height }) => { - self.resize_window(width, height); + let _ = self.cef_view_info_sender.send(cef::ViewInfoUpdate::Size { width, height }); + self.cef_context.notify_view_info_changed(); + + if let Some(render_state) = &mut self.render_state { + render_state.resize(width, height); + } if let Some(window) = &self.window { let maximized = window.is_maximized(); self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(DesktopWrapperMessage::UpdateMaximized { maximized })); + + window.request_redraw(); } } WindowEvent::ScaleFactorChanged { scale_factor, .. } => {