···00001use we_platform::appkit;
2-use we_platform::cg::{BitmapContext, CGRect};
00000000000000000000000000000000000000000000000000000000000000000000000000000034fn main() {
0000000000000000000005 let _pool = appkit::AutoreleasePool::new();
67 let app = appkit::App::shared();
8 app.set_activation_policy(appkit::NS_APPLICATION_ACTIVATION_POLICY_REGULAR);
9-10 appkit::install_app_delegate(&app);
1112 let window = appkit::create_standard_window("we");
13-14- // Install a window delegate to handle resize events.
15 appkit::install_window_delegate(&window);
16-17- // Enable mouse-moved event delivery so mouseMoved: fires on the view.
18 window.set_accepts_mouse_moved_events(true);
1920- // Create a bitmap context for software rendering.
21- let bitmap = BitmapContext::new(800, 600).expect("failed to create bitmap context");
22-23- // Draw a colored rectangle as proof of life.
24- // Clear to dark gray background.
25- bitmap.clear(0.15, 0.15, 0.15, 1.0);
26- // Draw a blue rectangle in the center.
27- bitmap.fill_rect(CGRect::new(200.0, 150.0, 400.0, 300.0), 0.2, 0.4, 0.8, 1.0);
2829- // Create a custom view backed by the bitmap context and set it as
30- // the window's content view.
31 let frame = appkit::NSRect::new(0.0, 0.0, 800.0, 600.0);
32 let view = appkit::BitmapView::new(frame, &bitmap);
33 window.set_content_view(&view.id());
00000000000003435 window.make_key_and_order_front();
36 app.activate();
···1+use std::cell::RefCell;
2+3+use we_html::parse_html;
4+use we_layout::layout;
5use we_platform::appkit;
6+use we_platform::cg::BitmapContext;
7+use we_render::Renderer;
8+use we_text::font::{self, Font};
9+10+/// Default HTML page shown when no file argument is provided.
11+const DEFAULT_HTML: &str = r#"<!DOCTYPE html>
12+<html>
13+<head><title>we browser</title></head>
14+<body>
15+<h1>Hello from we!</h1>
16+<p>This is a from-scratch web browser engine written in pure Rust.</p>
17+<p>Zero external crate dependencies. Every subsystem is implemented in Rust.</p>
18+<h2>Features</h2>
19+<p>HTML5 tokenizer, DOM tree, block layout, and software rendering.</p>
20+</body>
21+</html>"#;
22+23+/// Browser state kept in thread-local storage so the resize handler can
24+/// access it. All AppKit callbacks run on the main thread.
25+struct BrowserState {
26+ html: String,
27+ font: Font,
28+ bitmap: Box<BitmapContext>,
29+ view: appkit::BitmapView,
30+}
31+32+thread_local! {
33+ static STATE: RefCell<Option<BrowserState>> = const { RefCell::new(None) };
34+}
35+36+/// Re-run the full pipeline: parse → layout → render → copy to bitmap.
37+fn render_page(html: &str, font: &Font, bitmap: &mut BitmapContext) {
38+ let width = bitmap.width() as u32;
39+ let height = bitmap.height() as u32;
40+ if width == 0 || height == 0 {
41+ return;
42+ }
43+44+ let doc = parse_html(html);
45+ let tree = layout(&doc, width as f32, height as f32, font);
46+47+ let mut renderer = Renderer::new(width, height);
48+ renderer.paint(&tree, font);
49+50+ // Copy rendered pixels into the bitmap context's buffer.
51+ let src = renderer.pixels();
52+ let dst = bitmap.pixels_mut();
53+ let len = src.len().min(dst.len());
54+ dst[..len].copy_from_slice(&src[..len]);
55+}
56+57+/// Called by the platform crate when the window is resized.
58+fn handle_resize(width: f64, height: f64) {
59+ STATE.with(|state| {
60+ let mut state = state.borrow_mut();
61+ let state = match state.as_mut() {
62+ Some(s) => s,
63+ None => return,
64+ };
65+66+ let w = width as usize;
67+ let h = height as usize;
68+ if w == 0 || h == 0 {
69+ return;
70+ }
71+72+ // Create a new bitmap context with the new dimensions.
73+ let mut new_bitmap = match BitmapContext::new(w, h) {
74+ Some(b) => Box::new(b),
75+ None => return,
76+ };
77+78+ render_page(&state.html, &state.font, &mut new_bitmap);
79+80+ // Swap in the new bitmap and update the view's pointer.
81+ state.bitmap = new_bitmap;
82+ state.view.update_bitmap(&state.bitmap);
83+ });
84+}
8586fn main() {
87+ // Load HTML from file argument or use default page.
88+ let html = match std::env::args().nth(1) {
89+ Some(path) => match std::fs::read_to_string(&path) {
90+ Ok(content) => content,
91+ Err(e) => {
92+ eprintln!("Error reading {}: {}", path, e);
93+ std::process::exit(1);
94+ }
95+ },
96+ None => DEFAULT_HTML.to_string(),
97+ };
98+99+ // Load a system font for text rendering.
100+ let font = match font::load_system_font() {
101+ Ok(f) => f,
102+ Err(e) => {
103+ eprintln!("Error loading system font: {:?}", e);
104+ std::process::exit(1);
105+ }
106+ };
107+108 let _pool = appkit::AutoreleasePool::new();
109110 let app = appkit::App::shared();
111 app.set_activation_policy(appkit::NS_APPLICATION_ACTIVATION_POLICY_REGULAR);
0112 appkit::install_app_delegate(&app);
113114 let window = appkit::create_standard_window("we");
00115 appkit::install_window_delegate(&window);
00116 window.set_accepts_mouse_moved_events(true);
117118+ // Initial render at the default window size (800x600).
119+ let mut bitmap =
120+ Box::new(BitmapContext::new(800, 600).expect("failed to create bitmap context"));
121+ render_page(&html, &font, &mut bitmap);
0000122123+ // Create the view backed by the rendered bitmap.
0124 let frame = appkit::NSRect::new(0.0, 0.0, 800.0, 600.0);
125 let view = appkit::BitmapView::new(frame, &bitmap);
126 window.set_content_view(&view.id());
127+128+ // Store state for the resize handler.
129+ STATE.with(|state| {
130+ *state.borrow_mut() = Some(BrowserState {
131+ html,
132+ font,
133+ bitmap,
134+ view,
135+ });
136+ });
137+138+ // Register resize handler so re-layout happens on window resize.
139+ appkit::set_resize_handler(handle_resize);
140141 window.make_key_and_order_front();
142 app.activate();
+47-1
crates/platform/src/appkit.rs
···458 BitmapView { view }
459 }
4600000000000000461 /// Request the view to redraw.
462 ///
463 /// Call this after modifying the bitmap context's pixels to
···473}
474475// ---------------------------------------------------------------------------
000000000000000000000000476// Window delegate for handling resize and close events
477// ---------------------------------------------------------------------------
478···490 let delegate_class = Class::allocate(superclass, c"WeWindowDelegate", 0)
491 .expect("failed to allocate WeWindowDelegate class");
492493- // windowDidResize: — mark the content view as needing display
494 extern "C" fn window_did_resize(
495 _this: *mut c_void,
496 _sel: *mut c_void,
···503 let content_view: *mut c_void = msg_send![win, contentView];
504 if content_view.is_null() {
505 return;
000000000506 }
507 let _: *mut c_void = msg_send![content_view, setNeedsDisplay: true];
508 }
···458 BitmapView { view }
459 }
460461+ /// Update the bitmap context pointer stored in the view.
462+ ///
463+ /// Call this when the bitmap context has been replaced (e.g., on resize).
464+ /// The new `BitmapContext` must outlive this view.
465+ pub fn update_bitmap(&self, bitmap_ctx: &BitmapContext) {
466+ unsafe {
467+ self.view.set_ivar(
468+ BITMAP_CTX_IVAR,
469+ bitmap_ctx as *const BitmapContext as *mut c_void,
470+ );
471+ }
472+ }
473+474 /// Request the view to redraw.
475 ///
476 /// Call this after modifying the bitmap context's pixels to
···486}
487488// ---------------------------------------------------------------------------
489+// Global resize handler
490+// ---------------------------------------------------------------------------
491+492+/// Global resize callback, called from `windowDidResize:` with the new
493+/// content view dimensions (width, height) in points.
494+///
495+/// # Safety
496+///
497+/// Accessed only from the main thread (the AppKit event loop).
498+static mut RESIZE_HANDLER: Option<fn(f64, f64)> = None;
499+500+/// Register a function to be called when the window is resized.
501+///
502+/// The handler receives the new content view width and height in points.
503+/// Only one handler can be active at a time; setting a new one replaces
504+/// any previous handler.
505+pub fn set_resize_handler(handler: fn(f64, f64)) {
506+ // SAFETY: Called from the main thread before `app.run()`.
507+ unsafe {
508+ RESIZE_HANDLER = Some(handler);
509+ }
510+}
511+512+// ---------------------------------------------------------------------------
513// Window delegate for handling resize and close events
514// ---------------------------------------------------------------------------
515···527 let delegate_class = Class::allocate(superclass, c"WeWindowDelegate", 0)
528 .expect("failed to allocate WeWindowDelegate class");
529530+ // windowDidResize: — call resize handler and mark view as needing display
531 extern "C" fn window_did_resize(
532 _this: *mut c_void,
533 _sel: *mut c_void,
···540 let content_view: *mut c_void = msg_send![win, contentView];
541 if content_view.is_null() {
542 return;
543+ }
544+ // Get the content view's bounds to determine new dimensions.
545+ let bounds: NSRect = msg_send![content_view, bounds];
546+ // Call the resize handler if one has been registered.
547+ // SAFETY: We are on the main thread (AppKit event loop).
548+ unsafe {
549+ if let Some(handler) = RESIZE_HANDLER {
550+ handler(bounds.size.width, bounds.size.height);
551+ }
552 }
553 let _: *mut c_void = msg_send![content_view, setNeedsDisplay: true];
554 }