| name | robius-widget-patterns |
| description | ALWAYS use this when the request matches Robius Widget Patterns: CRITICAL: Use for Robius widget patterns. |
Robius Widget Patterns Skill
Selective Reading Rule
Start with:
references/senior-master-standard.md
references/usage-routing.md
references/quality-checklist.md
Then load only the inherited docs, scripts, assets, or examples that match the user's actual task.
Best practices for designing reusable Makepad widgets based on Robrix and Moly codebase patterns.
Source codebases:
- Robrix: Matrix chat client - Avatar, RoomsList, RoomScreen widgets
- Moly: AI chat application - Slot, ChatLine, PromptInput, AdaptiveView widgets
When to Use
Use this skill when:
- Creating reusable Makepad widgets
- Designing widget component APIs
- Implementing text/image toggle patterns
- Dynamic styling in Makepad
- Keywords: robrix widget, makepad component, reusable widget, widget design pattern
Production Patterns
For production-ready widget patterns, see the _base/ directory:
| Pattern | Description |
|---|
| 01-widget-extension | Add helper methods to widget references |
| 02-modal-overlay | Popups, dialogs using DrawList2d overlay |
| 03-collapsible | Expandable/collapsible sections |
| 04-list-template | Dynamic lists with LivePtr templates |
| 05-lru-view-cache | Memory-efficient view caching |
| 14-callout-tooltip | Tooltips with arrow positioning |
| 20-redraw-optimization | Efficient redraw patterns |
| 15-dock-studio-layout | IDE-style resizable panels |
| 16-hover-effect | Hover effects with instance variables |
| 17-row-based-grid-layout | Dynamic grid layouts |
| 18-drag-drop-reorder | Drag-and-drop widget reordering |
| 19-pageflip-optimization | PageFlip 切换优化,即刻销毁/缓存模式 |
| 21-collapsible-row-portal-list | Auto-grouping consecutive items in portal lists with FoldHeader |
| 22-dropdown-overlay | Dropdown popups using DrawList2d overlay (no layout push) |
Standard Widget Structure
use makepad_widgets::*;
live_design! {
use link::theme::*;
use link::widgets::*;
pub MyWidget = {{MyWidget}} {
width: Fill, height: Fit,
flow: Down,
inner_view = <View> {
}
}
}
#[derive(Live, LiveHook, Widget)]
pub struct MyWidget {
#[deref] view: View,
#[live] some_property: f64,
#[live(100.0)] default_val: f64,
#[rust] internal_state: State,
#[animator] animator: Animator,
}
impl Widget for MyWidget {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
}
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
self.view.draw_walk(cx, scope, walk)
}
}
Text/Image Toggle Pattern
A common pattern for widgets that show either text or an image (like avatars):
live_design! {
pub Avatar = {{Avatar}} {
width: 36.0, height: 36.0,
align: { x: 0.5, y: 0.5 }
flow: Overlay,
text_view = <View> {
visible: true,
show_bg: true,
draw_bg: {
uniform background_color: #888888
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size);
let c = self.rect_size * 0.5;
sdf.circle(c.x, c.x, c.x)
sdf.fill_keep(self.background_color);
return sdf.result
}
}
text = <Label> {
text: "?"
}
}
img_view = <View> {
visible: false,
img = <Image> {
fit: Stretch,
width: Fill, height: Fill,
}
}
}
}
#[derive(LiveHook, Live, Widget)]
pub struct Avatar {
#[deref] view: View,
#[rust] info: Option<UserInfo>,
}
impl Avatar {
pub <T: <>>(
& ,
cx: & Cx,
bg_color: <Vec4>,
info: <AvatarTextInfo>,
username: T,
) {
.info = info.(|i| i.());
= utils::(username.())
.().();
.(ids!(text_view.text)).(cx, &first_char);
.(ids!(text_view)).(cx, );
.(ids!(img_view)).(cx, );
(color) = bg_color {
.(ids!(text_view)).(cx, live! {
draw_bg: { background_color: (color) }
});
}
}
<F, E>(
& ,
cx: & Cx,
info: <AvatarImageInfo>,
image_set_fn: F,
) <(), E>
F: (& Cx, ImageRef) <(), E>
{
= .(ids!(img_view.img));
= (cx, img_ref);
res.() {
.(ids!(img_view)).(cx, );
.(ids!(text_view)).(cx, );
.info = info.(|i| i.());
}
res
}
(& ) DisplayStatus {
.(ids!(img_view)).() {
DisplayStatus::Image
} {
DisplayStatus::Text
}
}
}
Dynamic Styling with apply_over
Apply dynamic styles at runtime:
self.view(ids!(content)).apply_over(cx, live! {
draw_bg: { color: #ff0000 }
});
self.view(ids!(message)).apply_over(cx, live! {
padding: { left: 20, right: 20 }
margin: { top: 10 }
});
let highlight_color = if is_selected { vec4(1.0, 0.0, 0.0, 1.0) } else { vec4(0.5, 0.5, 0.5, 1.0) };
self.view(ids!(item)).apply_over(cx, live! {
draw_bg: { color: (highlight_color) }
});
Widget Reference Pattern
Implement *Ref methods for external API:
impl AvatarRef {
pub fn show_text<T: AsRef<str>>(
&self,
cx: &mut Cx,
bg_color: Option<Vec4>,
info: Option<AvatarTextInfo>,
username: T,
) {
if let Some(mut inner) = self.borrow_mut() {
inner.show_text(cx, bg_color, info, username);
}
}
pub fn show_image<F, E>(
&self,
cx: &mut Cx,
info: Option<AvatarImageInfo>,
image_set_fn: F,
) -> Result<(), E>
where
F: FnOnce(&mut Cx, ImageRef) -> Result<(), E>
{
if let Some(mut inner) = self.borrow_mut() {
inner.show_image(cx, info, image_set_fn)
} else {
Ok(())
}
}
}
Collapsible/Expandable Pattern
live_design! {
pub CollapsibleSection = {{CollapsibleSection}} {
flow: Down,
header = <View> {
cursor: Hand,
icon = <Icon> { }
title = <Label> { text: "Section" }
}
content = <View> {
visible: false,
}
}
}
#[derive(Live, LiveHook, Widget)]
pub struct CollapsibleSection {
#[deref] view: View,
#[rust] is_expanded: bool,
}
impl CollapsibleSection {
pub fn toggle(&mut self, cx: &mut Cx) {
self.is_expanded = !self.is_expanded;
self.view(ids!(content)).set_visible(cx, self.is_expanded);
let rotation = if self.is_expanded { 90.0 } else { 0.0 };
self.view(ids!(header.icon)).apply_over(cx, live! {
draw_icon: { rotation: (rotation) }
});
self.redraw(cx);
}
}
Loading State Pattern
live_design! {
pub LoadableContent = {{LoadableContent}} {
flow: Overlay,
content = <View> {
visible: true,
}
loading_overlay = <View> {
visible: false,
show_bg: true,
draw_bg: { color: #00000088 }
align: { x: 0.5, y: 0.5 }
<BouncingDots> { }
}
error_view = <View> {
visible: false,
error_label = <Label> { }
}
}
}
#[derive(Live, LiveHook, Widget)]
pub struct LoadableContent {
#[deref] view: View,
#[rust] state: LoadingState,
}
pub enum LoadingState {
Idle,
Loading,
Loaded,
Error(String),
}
impl LoadableContent {
pub fn set_state(&mut self, cx: &mut Cx, state: LoadingState) {
self.state = state;
match &self.state {
LoadingState::Idle | LoadingState::Loaded => {
self.view(ids!(content)).set_visible(cx, true);
self.view(ids!(loading_overlay)).set_visible(cx, false);
self.view(ids!(error_view)).(cx, );
}
LoadingState::Loading => {
.(ids!(content)).(cx, );
.(ids!(loading_overlay)).(cx, );
.(ids!(error_view)).(cx, );
}
LoadingState::(msg) => {
.(ids!(content)).(cx, );
.(ids!(loading_overlay)).(cx, );
.(ids!(error_view)).(cx, );
.(ids!(error_view.error_label)).(cx, msg);
}
}
.(cx);
}
}
PortalList Item Pattern
For virtual list items:
live_design! {
pub ItemsList = {{ItemsList}} {
list = <PortalList> {
keep_invisible: false,
auto_tail: false,
width: Fill, height: Fill,
flow: Down,
item_entry = <ItemEntry> {}
header = <SectionHeader> {}
empty = <View> {}
}
}
}
impl Widget for ItemsList {
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
while let Some(item) = self.view.draw_walk(cx, scope, walk).step() {
if let Some(mut list) = item.as_portal_list().borrow_mut() {
list.set_item_range(cx, 0, self.items.len());
while let Some(item_id) = list.next_visible_item(cx) {
let item = list.item(cx, item_id, live_id!(item_entry));
self.populate_item(cx, item, &self.items[item_id]);
item.(cx, scope);
}
}
}
DrawStep::()
}
}
Best Practices
- Use
#[deref] for delegation: Delegate to inner View for standard behavior
- Separate DSL properties (
#[live]) from Rust state (#[rust])
- Implement both inner methods and
*Ref wrappers
- Use
apply_over for dynamic runtime styling
- Use
flow: Overlay for toggle/swap patterns
- Use
set_visible() to toggle between alternative views
- Always call
redraw(cx) after state changes
Reference Files
references/widget-patterns.md - Additional widget patterns (Robrix)
references/styling-patterns.md - Dynamic styling patterns (Robrix)
references/moly-widget-patterns.md - Moly-specific patterns
Slot widget for runtime content replacement
MolyRoot conditional rendering wrapper
AdaptiveView for responsive Mobile/Desktop layouts
- Chat line variants (UserLine, BotLine, ErrorLine, etc.)
CommandTextInput with action buttons
- Sidebar navigation with radio buttons
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.