Let's explain the drawing process through a simple Button widget:
1pubstructButton{2// Drawing state machine3#[rust] draw_state:DrawStateWrap<DrawState>,4// Layout information5#[layout] layout:Layout,6// Positioning information7#[walk] walk:Walk,8// Background drawing9#[live] draw_bg:DrawColor,10// Text drawing11#[live] draw_text:DrawText,12}1314implWidgetforButton{15fndraw_walk(&mutself, cx:&mutCx2d, scope:&mutScope, walk:Walk)->DrawStep{16// 1. Initialize drawing state17ifself.draw_state.begin(cx,DrawState::DrawBackground){18// Start layout calculation19self.draw_bg.begin(cx, walk,self.layout);20returnDrawStep::make_step();21}2223// 2. Draw background24ifletSome(DrawState::DrawBackground)=self.draw_state.get(){25self.draw_bg.end(cx);26// Switch to text drawing state27self.draw_state.set(DrawState::DrawText);28returnDrawStep::make_step();29}3031// 3. Draw text32ifletSome(DrawState::DrawText)=self.draw_state.get(){33let text_walk =Walk::size(Size::Fill,Size::Fit);34self.draw_text.draw_walk(cx, scope, text_walk)?;35self.draw_state.end();36}3738DrawStep::done()39}40}4142// Drawing state enumeration43#[derive(Clone)]44enumDrawState{45DrawBackground,46DrawText47}
Key points analysis:
State Management
DrawStateWrap manages drawing state
Each state corresponds to a drawing phase
Can interrupt and resume at any stage
Layout System
1// Set layout2self.draw_bg.begin(cx, walk,self.layout);34// Turtle automatically handles:5// - Element position calculation6// - Margin/Padding handling7// - Child element layout
Makepad employs a sophisticated but efficient deferred drawing system.
The core feature of this system is that drawing commands are not executed immediately, but are collected into a draw list (DrawList) for batch processing.
DrawList is the core of Makepad's drawing system. Let's look at its structure:
When we call a redraw command, here's what actually happens:
1implDrawQuad{2pubfndraw(&mutself, cx:&mutCx2d){3// 1. Not immediate drawing, but collecting draw commands4ifletSome(mi)=&mutself.many_instances {5// Batch processing mode: Add instance data to buffer6// This allows multiple similar draw operations to be batch processed,7// greatly reducing GPU calls8 mi.instances.extend_from_slice(self.draw_vars.as_slice());9}10elseifself.draw_vars.can_instance(){11// Single instance mode: Create new instance12let new_area = cx.add_aligned_instance(&self.draw_vars);13self.draw_vars.area = cx.update_area_refs(self.draw_vars.area, new_area);14}15}16}
In Makepad, the core of area management is the Area type, which is used to track and manage the drawing area of widgets on screen.
Each Widget has an associated Area, which is used not only for determining drawing position but also for event handling (Event) and hit detection.
1// Core area type definition2#[derive(Clone, Copy, Debug)]3pubenumArea{4Empty,5Instance(InstanceArea),// Instance area (for rendering instances)6Rect(RectArea)// Rectangle area (for basic shapes)7}89pubstructRectArea{10pub draw_list_id:DrawListId,11pub redraw_id:u64,12pub rect_id:usize13}1415pubstructInstanceArea{16pub draw_list_id:DrawListId,17pub draw_item_id:usize,18pub instance_count:usize,19pub instance_offset:usize,20pub redraw_id:u6421}
The lifecycle of an Area can be visualized as follows:
Here's the core implementation of Area management:
Area Creation and Allocation:
1implWidget{2fndraw_walk(&mutself, cx:&mutCx2d, scope:&mutScope, walk:Walk)->DrawStep{3// Start with layout calculation4ifself.draw_state.begin(cx,DrawState::Begin){5// Begin layout calculation6 cx.begin_turtle(walk,self.layout);78// Area allocation9 cx.walk_turtle_with_area(&mutself.area, walk);1011// Area update and reference management12self.area = cx.update_area_refs(self.area, new_area);13}14}15}
UI rendering and event handling occur on the main thread
Main thread runs the event loop (event_loop)
All UI updates must happen on the main thread
Multiple Worker Threads
Background tasks are managed through thread pools
Multiple thread pool implementations available for different needs
Worker threads don't directly manipulate UI
Inter-thread Communication Mechanisms
Action system for thread message passing
Signal mechanism for thread synchronization
Channel for data transfer
Main thread (UI thread) model:
1// Main thread entry point defined in app_main macro2pubfnapp_main(){3// Create Cx4letmut cx =Rc::new(RefCell::new(Cx::new(Box::new(move|cx, event|{5// Main event handling loop6ifletEvent::Startup= event {7*app.borrow_mut()=Some($app::new_main(cx));8}9ifletEvent::LiveEdit= event {10 app.borrow_mut().update_main(cx);11}12 app.borrow_mut().as_mut().unwrap().handle_event(cx, event);13}))));1415// Register components, initialize etc.16$app::register_main_module(&mut*cx.borrow_mut());17live_design(&mut*cx.borrow_mut());18 cx.borrow_mut().init_cx_os();1920// Start event loop21Cx::event_loop(cx);22}
Inter-thread communication mechanisms:
1// Global Action send channel2staticACTION_SENDER_GLOBAL:Mutex<Option<Sender<ActionSendSync>>>=Mutex::new(None);34// UI signal mechanism5pubstructSignalToUI(Arc<AtomicBool>);67// Thread communication Receiver/Sender8pubstructToUIReceiver<T>{9 sender:Sender<T>,10pub receiver:Receiver<T>,11}1213pubstructToUISender<T>{14 sender:Sender<T>,15}
Thread pools included:
1// Standard thread pool for simple task execution2pubstructRevThreadPool{3 tasks:Arc<Mutex<Vec<Box<dynFnOnce()+Send+'static>>>>,4}56// Tagged thread pool for tasks that need categorization and cancellation7pubstructTagThreadPool<T:Clone+Send+'static+PartialEq>{8 tasks:Arc<Mutex<Vec<(T,Box<dynFnOnce(T)+Send+'static>)>>>,9}1011// Message thread pool for continuous inter-thread communication12pubstructMessageThreadPool<T:Clone+Send+'static>{13 sender:Sender<Box<dynFnOnce(Option<T>)+Send+'static>>,14 msg_senders:Vec<Sender<T>>,15}
Main communication flow:
1// 1. Worker thread sends Action to main thread2Cx::post_action(action);// Send via global ACTION_SENDER34// 2. Main thread processes received Actions5implCx{6pubfnhandle_action_receiver(&mutself){7whileletOk(action)=self.action_receiver.try_recv(){8self.new_actions.push(action);9}10self.handle_actions();11}12}1314// 3. UI state update notification15SignalToUI::set_ui_signal();// Notify UI that update is needed
Makepad provides an Event mechanism for bottom-up propagation (distributed from system/framework to components) of system-level events (like mouse, keyboard, touch, etc.).
Events are synchronously processed global events.
1pubenumEvent{2// Application lifecycle events3Startup,4Shutdown,5Foreground,6Background,7Resume,8Pause,910// UI interaction events11Draw(DrawEvent),12MouseDown(MouseDownEvent),13MouseMove(MouseMoveEvent),14KeyDown(KeyEvent),15TextInput(TextInputEvent),1617// Custom events18Signal,// For inter-thread communication19Actions(ActionsBuf),// Container for custom actions20Timer(TimerEvent),// Timer events21}
Additionally, Makepad provides an Action mechanism for top-down propagation (sent from components to parent components/listeners) of internal business actions.
These Actions can be either synchronous or asynchronous.
Summary of Event and Action differences:
Events are system-level input events, propagating bottom-up to transmit low-level events.
Actions are component-level business actions, propagating top-down to transmit business actions.
1// Action trait definition2pubtraitActionTrait:'static{3fndebug_fmt(&self, f:&mutfmt::Formatter<'_>)->fmt::Result;4fnref_cast_type_id(&self)->TypeId;5}67// Concrete Action example, defining Button's business code8#[derive(Clone, Debug)]9pubenumButtonAction{10Clicked,11Pressed,12Released13}
Makepad provides a unified Action sending and handling mechanism through widget_action, and Actions can carry data and state.
1// Action wrapper structure2pubstructWidgetAction{3pub action:Box<dynWidgetActionTrait>,4pub data:Option<Arc<dynActionTrait>>,// Associated data5pub widgets:SmallVec<[WidgetRef;4]>,// Widget references sending the action6pub widget_uid:WidgetUid,// Widget unique ID7pub path:HeapLiveIdPath,// Widget path8pub group:Option<WidgetActionGroup>// Group information9}1011// Action group information12pubstructWidgetActionGroup{13pub group_uid:WidgetUid,14pub item_uid:WidgetUid,15}
1// Capture all actions produced during an event handling process2let actions = cx.capture_actions(|cx|{3self.button.handle_event(cx, event, scope);4});
Then search for specific Actions:
1implWidgetActionsApiforActions{2// Find action by component path3fnwidget_action(&self, path:&[LiveId])->Option<&WidgetAction>{4for action inself{5ifletSome(action)= action.downcast_ref::<WidgetAction>(){6letmut ap = action.path.data.iter().rev();7if path.iter().rev().all(|p| ap.find(|&ap| p == ap).is_some()){8returnSome(action)9}10}11}12None13}1415// Find action by component ID16fnfind_widget_action(&self, widget_uid:WidgetUid)->Option<&WidgetAction>{17for action inself{18ifletSome(action)= action.downcast_ref::<WidgetAction>(){19if action.widget_uid == widget_uid {20returnSome(action);21}22}23}24None25}26}
Since the Widget trait's handle_event mainly focuses on two aspects:
1implWidgetforMyWidget{2fnhandle_event(&mutself, cx:&mutCx, event:&Event, scope:&mutScope){3// 1. Handle hit test events within area (clicks, drags, etc.)4match event.hits(cx,self.area()){5Hit::FingerDown(e)=>{...}6Hit::KeyDown(e)=>{...}7}89// 2. Handle animation-related events10ifself.animator_handle_event(cx, event).must_redraw(){11self.draw_key.area().redraw(cx)12}13}14}
However, there are actually many events that are unrelated to Area, such as:
Lifecycle events (startup, shutdown)
Global events (foreground, background switches)
Action handling
Drawing events
Animation frame updates
If all these events were handled in each Widget, the match event branches would be very redundant.
1// Without using MatchEvent2implWidgetforMyWidget{3fnhandle_event(&mutself, cx:&mutCx, event:&Event, scope:&mutScope){4match event {5Event::Startup=>{...}6Event::Draw(e)=>{...}7Event::NextFrame(e)=>{...}8Event::Actions(e)=>{...}9// Still need to handle hit testing10 _ =>match event.hits(cx,self.area()){11Hit::FingerDown(e)=>{...}12}13}14}15}
Therefore, Makepad provides the MatchEvent trait, which provides a series of default implementations to make the code clearer: