Skip to content

Commit

Permalink
Refactor event handling
Browse files Browse the repository at this point in the history
  • Loading branch information
fschutt committed Feb 14, 2025
1 parent 46d932b commit e6ab35d
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 22 deletions.
103 changes: 92 additions & 11 deletions azul-desktop/src/shell/appkit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ use std::ffi::{CStr, CString};
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use azul_core::app_resources::ImageCache;
use azul_core::gl::OptionGlContextPtr;
use azul_core::task::{Thread, ThreadId, Timer, TimerId};
use azul_core::ui_solver::QuickResizeResult;
use azul_core::window_state::NodesToCheck;
use azul_core::{FastBTreeSet, FastHashMap};
use gl_context_loader::GenericGlContext;
use objc2::declare::ClassDecl;
use objc2::runtime::{AnyClass, AnyObject, Class, ClassBuilder, Object, ProtocolObject, Sel};
use objc2::*;
use azul_core::window::{MacOSHandle, MonitorVec, ScrollResult, WindowInternal, WindowInternalInit};
use azul_core::window::{MacOSHandle, MonitorVec, PhysicalSize, ScrollResult, WindowInternal, WindowInternalInit};
use azul_core::window::WindowCreateOptions;
use crate::app::App;
use crate::app::{App, LazyFcCache};
use crate::wr_translate::{wr_synchronize_updated_images, AsyncHitTester};
use objc2::runtime::YES;
use objc2::rc::{autoreleasepool, AutoreleasePool, Retained};
Expand Down Expand Up @@ -56,15 +58,20 @@ use webrender::{
mod menu;
mod gl;

/// OpenGL context guard, to be returned by window.make_current_gl()
pub(crate) struct GlContextGuard {
glc: *mut Object,
}

pub(crate) struct MacApp {
pub(crate) functions: Rc<GenericGlContext>,
pub(crate) active_menus: BTreeMap<menu::MenuTarget, menu::CommandMap>,
pub(crate) data: Arc<Mutex<AppData>>,
}

struct AppData {
userdata: App,
windows: BTreeMap<WindowId, Window>
pub(crate) struct AppData {
pub userdata: App,
pub windows: BTreeMap<WindowId, Window>
}

pub(crate) struct Window {
Expand Down Expand Up @@ -97,6 +104,83 @@ pub(crate) struct Window {

impl Window {

// --- functions necessary for event.rs handling

/// Utility for making the GL context current, returning a guard that resets it afterwards
pub(crate) fn make_current_gl(gl_context: *mut Object) -> GlContextGuard {
unsafe {
let _: () = msg_send![gl_context, makeCurrentContext];
GlContextGuard { glc: gl_context }
}
}

/// Function that flushes the buffer and "ends" the drawing code
pub(crate) fn finish_gl(guard: GlContextGuard) {
unsafe {
let _: () = msg_send![guard.glc, flushBuffer];
}
}

/// On macOS, we might do something akin to `[self.ns_window setTitle:new_title]`
/// or update the menubar, etc.
pub(crate) fn update_menus(&mut self) {
// Called from `event::regenerate_dom` etc. after the DOM changes
}

/// After updating the display list, we want a new "hit tester" from webrender
pub(crate) fn request_new_hit_tester(&mut self) {
self.hit_tester = crate::wr_translate::AsyncHitTester::Requested(
self.render_api.request_hit_tester(crate::wr_translate::wr_translate_document_id(
self.internal.document_id
))
);
}

/// Indicate that the window contents need repainting (setNeedsDisplay: YES)
pub(crate) fn request_redraw(&mut self) {
unsafe {
if let Some(s) = self.ns_window.as_deref() {
let s: *mut Object = msg_send![s, contentView];
let _: () = msg_send![s, setNeedsDisplay: YES];
}
}
}

/// Called internally from do_resize
///
/// If needed, do `wr_synchronize_updated_images(resize_result.updated_images, ...)`
/// and update the WebRender doc-view
#[must_use]
pub(crate) fn do_resize_impl(
&mut self,
new_physical_size: PhysicalSize<u32>,
image_cache: &ImageCache,
fc_cache: &mut LazyFcCache,
gl_context_ptr: &OptionGlContextPtr,
) -> QuickResizeResult {

let new_size = new_physical_size.to_logical(self.internal.current_window_state.size.get_hidpi_factor());
let old_state = self.internal.current_window_state.clone();
self.internal.current_window_state.size.dimensions = new_size;

let size = self.internal.current_window_state.size.clone();
let theme = self.internal.current_window_state.theme.clone();

fc_cache.apply_closure(|fc_cache| {
self.internal.do_quick_resize(
image_cache,
&crate::app::CALLBACKS,
azul_layout::do_the_relayout,
fc_cache,
gl_context_ptr,
&size,
theme,
)
})
}

// --- functions necessary for process.rs handling

pub(crate) fn start_stop_timers(
&mut self,
added: FastHashMap<TimerId, Timer>,
Expand Down Expand Up @@ -436,7 +520,7 @@ fn create_nswindow(
})
};

wr_synchronize_updated_images(resize_result.updated_images, &document_id, &mut txn);
wr_synchronize_updated_images(resize_result.updated_images, &mut txn);

// glContext can be deactivated now
let _: () = unsafe { msg_send![gl_context, flushBuffer] };
Expand Down Expand Up @@ -890,15 +974,12 @@ fn draw_rect(this: *mut AnyObject, _sel: Sel, _dirty_rect: NSRect) {
// REDRAWING: if the width / height of the window differ from the display list w / h,
// relayout the code here (yes, in the redrawing function)

let gl_context: *mut Object = msg_send![this, openGLContext];
let _: () = msg_send![gl_context, makeCurrentContext];
let glc = Window::make_current_gl(msg_send![this, openGLContext]);

const GL_COLOR_BUFFER_BIT: u32 = 0x00004000;
ptr.functions.clear_color(0.0, 1.0, 0.0, 1.0);
ptr.functions.clear(GL_COLOR_BUFFER_BIT);

println!("redrawing window {windowid}");

let _: () = msg_send![gl_context, flushBuffer];
Window::finish_gl(glc);
}
}
191 changes: 191 additions & 0 deletions azul-desktop/src/shell/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use crate::app;
///! This module encapsulates the different "event actions" that were formerly
///! triggered by Windows messages such as `AZ_REGENERATE_DOM`, `AZ_REDO_HIT_TEST`,
///! and so on.
///
///! Instead of sending `PostMessageW(...)`, we call these event functions directly.
///! This way, the same logic is reusable for both Win32 and macOS.
use crate::wr_translate::{
generate_frame, rebuild_display_list, wr_synchronize_updated_images, wr_translate_document_id
};
use azul_core::app_resources::ResourceUpdate;
use azul_core::callbacks::LayoutCallbackInfo;
use azul_core::window_state::NodesToCheck;
use azul_core::window::{RawWindowHandle, WindowId};
use super::appkit::GlContextGuard;
use super::{AZ_TICK_REGENERATE_DOM, AZ_THREAD_TICK};

#[cfg(target_os = "macos")]
use crate::shell::appkit::Window;
#[cfg(target_os = "macos")]
use crate::shell::appkit::AppData;

#[cfg(target_os = "windows")]
use crate::shell::win32::Window;
#[cfg(target_os = "windows")]
use crate::shell::win32::AppData;

/// Regenerate the entire DOM (style, layout, etc.).
/// On Win32, this was triggered by `AZ_REGENERATE_DOM`.
pub fn regenerate_dom(window: &mut Window, appdata: &mut AppData, _guard: &GlContextGuard) {

// 2) Re-build the styled DOM (layout callback).
// This used to happen in the `WM_TIMER` or `AZ_REGENERATE_DOM` handler in Win32.
let mut resource_updates = Vec::new();
let mut ud = &mut appdata.userdata;
let fc_cache = &mut ud.fc_cache;
let dat = &mut ud.data;
let image_cache = &ud.image_cache;

{
let hit_tester = window.render_api
.request_hit_tester(wr_translate_document_id(window.internal.document_id))
.resolve();

let hit_tester_ref = &*hit_tester;
let did = window.internal.document_id;

fc_cache.apply_closure(|fc_cache| {
window.internal.regenerate_styled_dom(
dat,
image_cache,
&window.gl_context_ptr,
&mut resource_updates,
window.internal.get_dpi_scale_factor(),
&crate::app::CALLBACKS, // your user callbacks
fc_cache,
azul_layout::do_the_relayout,
// new hit-tester creation:
|window_state, _, layout_results| {
crate::wr_translate::fullhittest_new_webrender(
&*hit_tester_ref,
did,
window_state.focused_node,
layout_results,
&window_state.mouse_state.cursor_position,
window_state.size.get_hidpi_factor(),
)
},
);
});
}

// 3) Stop any timers associated with now-removed NodeIds.
window.stop_timers_with_node_ids();

// 4) Possibly update the menu bar (if your framework allows).
window.update_menus();

// 5) Rebuild display list after DOM update.
rebuild_display_list(&mut window.internal, &mut window.render_api, image_cache, resource_updates);

// 6) The new display list will produce a new hit-tester, schedule that.
window.request_new_hit_tester();

// 7) Since we've updated the entire DOM, we then want a new GPU render:
generate_frame(&mut window.internal, &mut window.render_api, true);

window.request_redraw(); // (if needed)

// 8) Done. The caller might also queue a second message to "AZ_REDO_HIT_TEST",
// but in this design, we can do the hit test here or just call
// event::redo_hit_test() as a separate function if you prefer.
}

/// Rebuild the display-list for the *existing* DOM (no layout reflow).
/// On Win32, triggered by `AZ_REGENERATE_DISPLAY_LIST`.
pub fn rebuild_display_list_only(window: &mut Window, appdata: &mut AppData, _guard: &GlContextGuard) {

let image_cache = &appdata.userdata.image_cache;

// No new resources, we only re-send the existing display-list
rebuild_display_list(&mut window.internal, &mut window.render_api, image_cache, vec![]);
window.request_new_hit_tester(); // refresh the hit-tester
generate_frame(&mut window.internal, &mut window.render_api, true);
window.request_redraw();
}

/// Re-run the hit-test logic after the display list is up-to-date,
/// previously triggered by `AZ_REDO_HIT_TEST` on Win32.
pub fn redo_hit_test(window: &mut Window, appdata: &mut AppData, _guard: &GlContextGuard) {

let new_hit_tester = window.render_api.request_hit_tester(
crate::wr_translate::wr_translate_document_id(window.internal.document_id),
);

window.hit_tester = crate::wr_translate::AsyncHitTester::Requested(new_hit_tester);

let hit = crate::wr_translate::fullhittest_new_webrender(
&*window.hit_tester.resolve(),
window.internal.document_id,
window.internal.current_window_state.focused_node,
&window.internal.layout_results,
&window.internal.current_window_state.mouse_state.cursor_position,
window.internal.current_window_state.size.get_hidpi_factor(),
);
window.internal.current_window_state.last_hit_test = hit;

// Possibly re-render or check callbacks that depend on the new hittest
window.request_redraw();
}

/// Rerun the "GPU scroll render" step, i.e. do any final re-draw calls needed.
/// On Win32, triggered by `AZ_GPU_SCROLL_RENDER`.
pub fn gpu_scroll_render(window: &mut Window, _appdata: &mut AppData, _guard: &GlContextGuard) {
generate_frame(&mut window.internal, &mut window.render_api, false);
window.request_redraw();
}

/// Called when the OS says "size changed" or "resized to new physical size",
/// merges logic from `WM_SIZE + the partial re-layout`.
pub fn do_resize(window: &mut Window, appdata: &mut AppData, new_width: u32, new_height: u32, _guard: &GlContextGuard) {

let new_physical_size = azul_core::window::PhysicalSize {
width: new_width,
height: new_height,
};

// TODO: check if size is above / below a certain bounds to trigger a DOM_REFRESH event
// (switching from desktop to mobile view)

let glc = window.gl_context_ptr.clone();

let resize = window.do_resize_impl(
new_physical_size,
&appdata.userdata.image_cache,
&mut appdata.userdata.fc_cache,
&glc,
);

if !resize.updated_images.is_empty() {
let mut txn = webrender::Transaction::new();
let did = wr_translate_document_id(window.internal.document_id);
wr_synchronize_updated_images(resize.updated_images, &mut txn);
window.render_api.send_transaction(did, txn);
}

// Rebuild display-list after resizing
rebuild_display_list(&mut window.internal, &mut window.render_api, &appdata.userdata.image_cache, vec![]);
window.request_new_hit_tester(); // Must re-request after size changed

generate_frame(&mut window.internal, &mut window.render_api, true);
window.request_redraw();
}

/// Called from your OS timer or thread events, merges logic from `WM_TIMER` + `AZ_THREAD_TICK`.
pub fn handle_timer_event(window: &mut Window, appdata: &mut AppData, timer_id: usize, guard: &GlContextGuard) {
// 1) Possibly dispatch user timers
// 2) Possibly handle "regenerate DOM" if it's the hot-reload timer
// ...
match timer_id {
AZ_TICK_REGENERATE_DOM => {
regenerate_dom(window, appdata, guard);
},
AZ_THREAD_TICK => {
// process threads
},
// or custom user TimerId => do callback, etc.
_ => { /* user timer logic */ }
}
}
7 changes: 7 additions & 0 deletions azul-desktop/src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ use webrender::Shaders as WrShaders;
use std::rc::Rc;
use std::cell::RefCell;

// ID sent by WM_TIMER to re-generate the DOM
const AZ_TICK_REGENERATE_DOM: usize = 1;
// ID sent by WM_TIMER to check the thread results
const AZ_THREAD_TICK: usize = 2;

pub(crate) mod process;
pub(crate) mod event;

#[cfg(target_os = "windows")]
pub mod win32;
#[cfg(target_os = "linux")]
Expand Down
7 changes: 4 additions & 3 deletions azul-desktop/src/shell/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,9 @@ pub(crate) fn process_callback_results(

if !updated_images.is_empty() {
let mut txn = WrTransaction::new();
wr_synchronize_updated_images(updated_images, &window.internal.document_id, &mut txn);
window.render_api.send_transaction(wr_translate_document_id(window.internal.document_id), txn);
let did = wr_translate_document_id(window.internal.document_id);
wr_synchronize_updated_images(updated_images, &mut txn);
window.render_api.send_transaction(did, txn);
result = result.max_self(ProcessEventResult::ShouldReRenderCurrentWindow);
}
}
Expand Down Expand Up @@ -306,7 +307,7 @@ pub(crate) fn process_callback_results(

if !updated_images.is_empty() {
let mut txn = WrTransaction::new();
wr_synchronize_updated_images(updated_images, &window.internal.document_id, &mut txn);
wr_synchronize_updated_images(updated_images, &mut txn);
window.render_api.send_transaction(wr_translate_document_id(window.internal.document_id), txn);
}
}
Expand Down
Loading

0 comments on commit e6ab35d

Please sign in to comment.