[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-3265f14d-8cc0-440f-b318-2fbd2b046601":3,"$fG804f6TxdGygvWbx-YME7Qdus9XehroRFY6tHC4sPpE":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"3265f14d-8cc0-440f-b318-2fbd2b046601","robius-widget-patterns","|","cat_life_career","mod_other","sickn33,other","---\nname: robius-widget-patterns\ndescription: |\n  CRITICAL: Use for Robius widget patterns. Triggers on:\n  apply_over, TextOrImage, modal, 可复用, 模态,\n  collapsible, drag drop, reusable widget, widget design,\n  pageflip, 组件设计, 组件模式\nrisk: unknown\nsource: community\n---\n\n# Robius Widget Patterns Skill\n\nBest practices for designing reusable Makepad widgets based on Robrix and Moly codebase patterns.\n\n**Source codebases:**\n- **Robrix**: Matrix chat client - Avatar, RoomsList, RoomScreen widgets\n- **Moly**: AI chat application - Slot, ChatLine, PromptInput, AdaptiveView widgets\n\n## When to Use\nUse this skill when:\n- Creating reusable Makepad widgets\n- Designing widget component APIs\n- Implementing text\u002Fimage toggle patterns\n- Dynamic styling in Makepad\n- Keywords: robrix widget, makepad component, reusable widget, widget design pattern\n\n## Production Patterns\n\nFor production-ready widget patterns, see the `_base\u002F` directory:\n\n| Pattern | Description |\n|---------|-------------|\n| 01-widget-extension | Add helper methods to widget references |\n| 02-modal-overlay | Popups, dialogs using DrawList2d overlay |\n| 03-collapsible | Expandable\u002Fcollapsible sections |\n| 04-list-template | Dynamic lists with LivePtr templates |\n| 05-lru-view-cache | Memory-efficient view caching |\n| 14-callout-tooltip | Tooltips with arrow positioning |\n| 20-redraw-optimization | Efficient redraw patterns |\n| 15-dock-studio-layout | IDE-style resizable panels |\n| 16-hover-effect | Hover effects with instance variables |\n| 17-row-based-grid-layout | Dynamic grid layouts |\n| 18-drag-drop-reorder | Drag-and-drop widget reordering |\n| 19-pageflip-optimization | PageFlip 切换优化，即刻销毁\u002F缓存模式 |\n| 21-collapsible-row-portal-list | Auto-grouping consecutive items in portal lists with FoldHeader |\n| 22-dropdown-overlay | Dropdown popups using DrawList2d overlay (no layout push) |\n\n## Standard Widget Structure\n\n```rust\nuse makepad_widgets::*;\n\nlive_design! {\n    use link::theme::*;\n    use link::widgets::*;\n\n    pub MyWidget = {{MyWidget}} {\n        width: Fill, height: Fit,\n        flow: Down,\n\n        \u002F\u002F Child widgets defined in DSL\n        inner_view = \u003CView> {\n            \u002F\u002F ...\n        }\n    }\n}\n\n#[derive(Live, LiveHook, Widget)]\npub struct MyWidget {\n    #[deref] view: View,              \u002F\u002F Delegate to inner View\n\n    #[live] some_property: f64,       \u002F\u002F DSL-configurable property\n    #[live(100.0)] default_val: f64,  \u002F\u002F With default value\n\n    #[rust] internal_state: State,    \u002F\u002F Rust-only state (not in DSL)\n\n    #[animator] animator: Animator,   \u002F\u002F For animations\n}\n\nimpl Widget for MyWidget {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {\n        self.view.handle_event(cx, event, scope);\n        \u002F\u002F Custom event handling...\n    }\n\n    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {\n        self.view.draw_walk(cx, scope, walk)\n    }\n}\n```\n\n## Text\u002FImage Toggle Pattern\n\nA common pattern for widgets that show either text or an image (like avatars):\n\n```rust\nlive_design! {\n    pub Avatar = {{Avatar}} {\n        width: 36.0, height: 36.0,\n        align: { x: 0.5, y: 0.5 }\n        flow: Overlay,  \u002F\u002F Stack views on top of each other\n\n        text_view = \u003CView> {\n            visible: true,  \u002F\u002F Default visible\n            show_bg: true,\n            draw_bg: {\n                uniform background_color: #888888\n                fn pixel(self) -> vec4 {\n                    let sdf = Sdf2d::viewport(self.pos * self.rect_size);\n                    let c = self.rect_size * 0.5;\n                    sdf.circle(c.x, c.x, c.x)\n                    sdf.fill_keep(self.background_color);\n                    return sdf.result\n                }\n            }\n            text = \u003CLabel> {\n                text: \"?\"\n            }\n        }\n\n        img_view = \u003CView> {\n            visible: false,  \u002F\u002F Hidden by default\n            img = \u003CImage> {\n                fit: Stretch,\n                width: Fill, height: Fill,\n            }\n        }\n    }\n}\n\n#[derive(LiveHook, Live, Widget)]\npub struct Avatar {\n    #[deref] view: View,\n    #[rust] info: Option\u003CUserInfo>,\n}\n\nimpl Avatar {\n    \u002F\u002F\u002F Show text content, hiding the image\n    pub fn show_text\u003CT: AsRef\u003Cstr>>(\n        &mut self,\n        cx: &mut Cx,\n        bg_color: Option\u003CVec4>,\n        info: Option\u003CAvatarTextInfo>,\n        username: T,\n    ) {\n        self.info = info.map(|i| i.into());\n\n        \u002F\u002F Get first character\n        let first_char = utils::first_letter(username.as_ref())\n            .unwrap_or(\"?\").to_uppercase();\n        self.label(ids!(text_view.text)).set_text(cx, &first_char);\n\n        \u002F\u002F Toggle visibility\n        self.view(ids!(text_view)).set_visible(cx, true);\n        self.view(ids!(img_view)).set_visible(cx, false);\n\n        \u002F\u002F Apply optional background color\n        if let Some(color) = bg_color {\n            self.view(ids!(text_view)).apply_over(cx, live! {\n                draw_bg: { background_color: (color) }\n            });\n        }\n    }\n\n    \u002F\u002F\u002F Show image content, hiding the text\n    pub fn show_image\u003CF, E>(\n        &mut self,\n        cx: &mut Cx,\n        info: Option\u003CAvatarImageInfo>,\n        image_set_fn: F,\n    ) -> Result\u003C(), E>\n    where\n        F: FnOnce(&mut Cx, ImageRef) -> Result\u003C(), E>\n    {\n        let img_ref = self.image(ids!(img_view.img));\n        let res = image_set_fn(cx, img_ref);\n\n        if res.is_ok() {\n            self.view(ids!(img_view)).set_visible(cx, true);\n            self.view(ids!(text_view)).set_visible(cx, false);\n            self.info = info.map(|i| i.into());\n        }\n        res\n    }\n\n    \u002F\u002F\u002F Check current display status\n    pub fn status(&mut self) -> DisplayStatus {\n        if self.view(ids!(img_view)).visible() {\n            DisplayStatus::Image\n        } else {\n            DisplayStatus::Text\n        }\n    }\n}\n```\n\n## Dynamic Styling with apply_over\n\nApply dynamic styles at runtime:\n\n```rust\n\u002F\u002F Apply single property\nself.view(ids!(content)).apply_over(cx, live! {\n    draw_bg: { color: #ff0000 }\n});\n\n\u002F\u002F Apply multiple properties\nself.view(ids!(message)).apply_over(cx, live! {\n    padding: { left: 20, right: 20 }\n    margin: { top: 10 }\n});\n\n\u002F\u002F Apply with variables\nlet highlight_color = if is_selected { vec4(1.0, 0.0, 0.0, 1.0) } else { vec4(0.5, 0.5, 0.5, 1.0) };\nself.view(ids!(item)).apply_over(cx, live! {\n    draw_bg: { color: (highlight_color) }\n});\n```\n\n## Widget Reference Pattern\n\nImplement `*Ref` methods for external API:\n\n```rust\nimpl AvatarRef {\n    \u002F\u002F\u002F See [`Avatar::show_text()`].\n    pub fn show_text\u003CT: AsRef\u003Cstr>>(\n        &self,\n        cx: &mut Cx,\n        bg_color: Option\u003CVec4>,\n        info: Option\u003CAvatarTextInfo>,\n        username: T,\n    ) {\n        if let Some(mut inner) = self.borrow_mut() {\n            inner.show_text(cx, bg_color, info, username);\n        }\n    }\n\n    \u002F\u002F\u002F See [`Avatar::show_image()`].\n    pub fn show_image\u003CF, E>(\n        &self,\n        cx: &mut Cx,\n        info: Option\u003CAvatarImageInfo>,\n        image_set_fn: F,\n    ) -> Result\u003C(), E>\n    where\n        F: FnOnce(&mut Cx, ImageRef) -> Result\u003C(), E>\n    {\n        if let Some(mut inner) = self.borrow_mut() {\n            inner.show_image(cx, info, image_set_fn)\n        } else {\n            Ok(())\n        }\n    }\n}\n```\n\n## Collapsible\u002FExpandable Pattern\n\n```rust\nlive_design! {\n    pub CollapsibleSection = {{CollapsibleSection}} {\n        flow: Down,\n\n        header = \u003CView> {\n            cursor: Hand,\n            icon = \u003CIcon> { }\n            title = \u003CLabel> { text: \"Section\" }\n        }\n\n        content = \u003CView> {\n            visible: false,\n            \u002F\u002F Expandable content here\n        }\n    }\n}\n\n#[derive(Live, LiveHook, Widget)]\npub struct CollapsibleSection {\n    #[deref] view: View,\n    #[rust] is_expanded: bool,\n}\n\nimpl CollapsibleSection {\n    pub fn toggle(&mut self, cx: &mut Cx) {\n        self.is_expanded = !self.is_expanded;\n        self.view(ids!(content)).set_visible(cx, self.is_expanded);\n\n        \u002F\u002F Rotate icon\n        let rotation = if self.is_expanded { 90.0 } else { 0.0 };\n        self.view(ids!(header.icon)).apply_over(cx, live! {\n            draw_icon: { rotation: (rotation) }\n        });\n\n        self.redraw(cx);\n    }\n}\n```\n\n## Loading State Pattern\n\n```rust\nlive_design! {\n    pub LoadableContent = {{LoadableContent}} {\n        flow: Overlay,\n\n        content = \u003CView> {\n            visible: true,\n            \u002F\u002F Main content\n        }\n\n        loading_overlay = \u003CView> {\n            visible: false,\n            show_bg: true,\n            draw_bg: { color: #00000088 }\n            align: { x: 0.5, y: 0.5 }\n            \u003CBouncingDots> { }\n        }\n\n        error_view = \u003CView> {\n            visible: false,\n            error_label = \u003CLabel> { }\n        }\n    }\n}\n\n#[derive(Live, LiveHook, Widget)]\npub struct LoadableContent {\n    #[deref] view: View,\n    #[rust] state: LoadingState,\n}\n\npub enum LoadingState {\n    Idle,\n    Loading,\n    Loaded,\n    Error(String),\n}\n\nimpl LoadableContent {\n    pub fn set_state(&mut self, cx: &mut Cx, state: LoadingState) {\n        self.state = state;\n        match &self.state {\n            LoadingState::Idle | LoadingState::Loaded => {\n                self.view(ids!(content)).set_visible(cx, true);\n                self.view(ids!(loading_overlay)).set_visible(cx, false);\n                self.view(ids!(error_view)).set_visible(cx, false);\n            }\n            LoadingState::Loading => {\n                self.view(ids!(content)).set_visible(cx, true);\n                self.view(ids!(loading_overlay)).set_visible(cx, true);\n                self.view(ids!(error_view)).set_visible(cx, false);\n            }\n            LoadingState::Error(msg) => {\n                self.view(ids!(content)).set_visible(cx, false);\n                self.view(ids!(loading_overlay)).set_visible(cx, false);\n                self.view(ids!(error_view)).set_visible(cx, true);\n                self.label(ids!(error_view.error_label)).set_text(cx, msg);\n            }\n        }\n        self.redraw(cx);\n    }\n}\n```\n\n## PortalList Item Pattern\n\nFor virtual list items:\n\n```rust\nlive_design! {\n    pub ItemsList = {{ItemsList}} {\n        list = \u003CPortalList> {\n            keep_invisible: false,\n            auto_tail: false,\n            width: Fill, height: Fill,\n            flow: Down,\n\n            \u002F\u002F Item templates\n            item_entry = \u003CItemEntry> {}\n            header = \u003CSectionHeader> {}\n            empty = \u003CView> {}\n        }\n    }\n}\n\nimpl Widget for ItemsList {\n    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {\n        while let Some(item) = self.view.draw_walk(cx, scope, walk).step() {\n            if let Some(mut list) = item.as_portal_list().borrow_mut() {\n                list.set_item_range(cx, 0, self.items.len());\n\n                while let Some(item_id) = list.next_visible_item(cx) {\n                    let item = list.item(cx, item_id, live_id!(item_entry));\n                    \u002F\u002F Populate item with data\n                    self.populate_item(cx, item, &self.items[item_id]);\n                    item.draw_all(cx, scope);\n                }\n            }\n        }\n        DrawStep::done()\n    }\n}\n```\n\n## Best Practices\n\n1. **Use `#[deref]` for delegation**: Delegate to inner View for standard behavior\n2. **Separate DSL properties (`#[live]`) from Rust state (`#[rust]`)**\n3. **Implement both inner methods and `*Ref` wrappers**\n4. **Use `apply_over` for dynamic runtime styling**\n5. **Use `flow: Overlay` for toggle\u002Fswap patterns**\n6. **Use `set_visible()` to toggle between alternative views**\n7. **Always call `redraw(cx)` after state changes**\n\n## Reference Files\n\n- `references\u002Fwidget-patterns.md` - Additional widget patterns (Robrix)\n- `references\u002Fstyling-patterns.md` - Dynamic styling patterns (Robrix)\n- `references\u002Fmoly-widget-patterns.md` - Moly-specific patterns\n  - `Slot` widget for runtime content replacement\n  - `MolyRoot` conditional rendering wrapper\n  - `AdaptiveView` for responsive Mobile\u002FDesktop layouts\n  - Chat line variants (UserLine, BotLine, ErrorLine, etc.)\n  - `CommandTextInput` with action buttons\n  - Sidebar navigation with radio buttons\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,110,2048,"2026-05-16 13:37:20",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"1f4d4b21-c319-4702-aa75-0081b2ce1c24","1.0.0","robius-widget-patterns.zip",4231,"uploads\u002Fskills\u002F3265f14d-8cc0-440f-b318-2fbd2b046601\u002Frobius-widget-patterns.zip","6cf30adc0e4e02c9f70ef00093e7c3ac38bd265cd437246b8888c461656f4fb2","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":12496}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]