一键导入
iced-widget-development
Comprehensive guide for developing custom widgets in Iced 0.14. Invoke when user needs to create or modify Iced widgets.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Comprehensive guide for developing custom widgets in Iced 0.14. Invoke when user needs to create or modify Iced widgets.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | iced-widget-development |
| description | Comprehensive guide for developing custom widgets in Iced 0.14. Invoke when user needs to create or modify Iced widgets. |
This skill provides comprehensive guidance for developing custom widgets using Iced 0.14's Widget trait.
ALWAYS invoke this skill when:
The Widget trait in Iced 0.14 requires implementing these methods:
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for MyWidget {
// Required methods:
fn size(&self) -> Size<Length>;
fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) -> layout::Node;
fn draw(&self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle);
fn update(&mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle);
fn mouse_interaction(&self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer) -> mouse::Interaction;
fn tag(&self) -> tree::Tag;
fn state(&self) -> tree::State;
}
Iced uses the Tree structure to manage widget state:
// Define your state type
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum MyWidgetState {
#[default]
Idle,
Hover,
Dragging { start_x: f32, start_y: f32 },
}
// In your widget struct, implement tag() and state():
pub struct MyWidget {
// ... fields
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for MyWidget {
fn tag(&self) -> tree::Tag {
tree::Tag::of::<MyWidgetState>()
}
fn state(&self) -> tree::State {
tree::State::new(MyWidgetState::default())
}
}
fn update(
&mut self,
tree: &mut Tree,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) {
let state = tree.state.downcast_mut::<MyWidgetState>();
let bounds = layout.bounds();
if let Event::Mouse(mouse_event) = event {
match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => {
if let Some(position) = cursor.position() {
if bounds.contains(position) {
*state = MyWidgetState::Dragging {
start_x: position.x,
start_y: position.y,
};
shell.request_redraw();
}
}
}
mouse::Event::ButtonReleased(mouse::Button::Left) => {
if matches!(state, MyWidgetState::Dragging { .. }) {
*state = MyWidgetState::Idle;
shell.request_redraw();
}
}
mouse::Event::CursorMoved { .. } => {
match *state {
MyWidgetState::Dragging { start_x, start_y } => {
if let Some(position) = cursor.position() {
let delta_x = position.x - start_x;
let delta_y = position.y - start_y;
// Handle dragging logic
shell.publish(Message::MyAction(delta_x, delta_y));
}
}
_ => {
let is_hover = if let Some(position) = cursor.position() {
bounds.contains(position)
} else {
false
};
let new_state = if is_hover {
MyWidgetState::Hover
} else {
MyWidgetState::Idle
};
if *state != new_state {
*state = new_state;
shell.request_redraw();
}
}
}
}
_ => {}
}
}
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let palette = theme.extended_palette();
let state = tree.state.downcast_ref::<MyWidgetState>();
// Determine color based on state
let color = match state {
MyWidgetState::Dragging { .. } => palette.primary.strong.color,
MyWidgetState::Hover => palette.primary.base.color,
MyWidgetState::Idle => palette.primary.weak.color,
};
// Draw quad
renderer.fill_quad(
Quad {
bounds,
border: Border {
radius: 4.0.into(),
width: 1.0,
color: palette.strong.color,
},
shadow: iced_core::Shadow::default(),
snap: false,
},
Background::Color(color),
);
}
fn mouse_interaction(
&self,
tree: &Tree,
_layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<MyWidgetState>();
match state {
MyWidgetState::Dragging { .. } => mouse::Interaction::Grabbing,
MyWidgetState::Hover => mouse::Interaction::Pointer,
_ => mouse::Interaction::default(),
}
}
Instead of manually calculating RGB values, use Iced's palette system:
let palette = theme.extended_palette();
// Use palette hierarchy for consistent theming
palette.primary.strong.color // Strongest color
palette.primary.base.color // Base color
palette.primary.weak.color // Weakest color
palette.background.weak.color
palette.text.base.color
Use .clamp() instead of .max().min():
// Before:
let value = (x / max).max(0.0).min(1.0);
// After:
let value = (x / max).clamp(0.0, 1.0);
Replace simple match expressions with matches!:
// Before:
match value {
1 | 3 | 5 => true,
_ => false,
}
// After:
matches!(value, 1 | 3 | 5)
Replace single-pattern match with if let:
// Before:
match event {
Event::Mouse(mouse_event) => {
// handle mouse event
}
_ => {}
}
// After:
if let Event::Mouse(mouse_event) = event {
// handle mouse event
}
// Before:
if condition1 {
// ...
} else {
if condition2 {
// ...
} else {
// ...
}
}
// After:
if condition1 {
// ...
} else if condition2 {
// ...
} else {
// ...
}
// Before:
widget.view(|value| Message::Update(value))
// After:
widget.view(Message::Update)
#[derive(Default)] for state enums instead of manual implementation#[default] attribute to mark the default varianttree.state.downcast_ref() or tree.state.downcast_mut()shell.request_redraw()shell.publish() for parent communicationif let Some(position) = cursor.position()use iced_core::{Length, Rectangle, Size, mouse, Event, Renderer as _Renderer};
use iced_core::widget::Tree;
use iced_core::layout;
use iced_core::renderer::{self, Quad};
use iced_core::border::Border;
use iced_core::Background;
use crate::{Message, Renderer, Theme, Element};
use iced_core::{Length, Rectangle, Size, mouse, Event, Renderer as _Renderer};
use iced_core::widget::Tree;
use iced_core::layout;
use iced_core::renderer::{self, Quad};
use iced_core::border::Border;
use iced_core::Background;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum MyWidgetState {
#[default]
Idle,
Hover,
Dragging { start_x: f32, start_y: f32 },
}
pub struct MyWidget<'a> {
pub on_action: Box<dyn Fn(f32, f32) -> Message + 'a>,
}
impl<'a> MyWidget<'a> {
pub fn new(on_action: impl Fn(f32, f32) -> Message + 'a) -> Self {
Self {
on_action: Box::new(on_action),
}
}
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for MyWidget<'_> {
fn size(&self) -> Size<Length> {
Size::new(Length::Fill, Length::Fill)
}
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
tree.state = self.state();
layout::Node::new(limits.max())
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let palette = theme.extended_palette();
let state = tree.state.downcast_ref::<MyWidgetState>();
let color = match state {
MyWidgetState::Dragging { .. } => palette.primary.strong.color,
MyWidgetState::Hover => palette.primary.base.color,
MyWidgetState::Idle => palette.primary.weak.color,
};
renderer.fill_quad(
Quad {
bounds,
border: Border::default(),
shadow: iced_core::Shadow::default(),
snap: false,
},
Background::Color(color),
);
}
fn update(
&mut self,
tree: &mut Tree,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn iced_core::Clipboard,
shell: &mut iced_core::Shell<'_, Message>,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_mut::<MyWidgetState>();
let bounds = layout.bounds();
if let Event::Mouse(mouse_event) = event {
match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => {
if let Some(position) = cursor.position() {
if bounds.contains(position) {
*state = MyWidgetState::Dragging {
start_x: position.x,
start_y: position.y,
};
shell.request_redraw();
}
}
}
mouse::Event::ButtonReleased(mouse::Button::Left) => {
if matches!(state, MyWidgetState::Dragging { .. }) {
*state = MyWidgetState::Idle;
shell.request_redraw();
}
}
mouse::Event::CursorMoved { .. } => {
match *state {
MyWidgetState::Dragging { start_x, start_y } => {
if let Some(position) = cursor.position() {
let delta_x = position.x - start_x;
let delta_y = position.y - start_y;
shell.publish((self.on_action)(delta_x, delta_y));
}
}
_ => {
let is_hover = if let Some(position) = cursor.position() {
bounds.contains(position)
} else {
false
};
let new_state = if is_hover {
MyWidgetState::Hover
} else {
MyWidgetState::Idle
};
if *state != new_state {
*state = new_state;
shell.request_redraw();
}
}
}
}
_ => {}
}
}
}
fn mouse_interaction(
&self,
tree: &Tree,
_layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<MyWidgetState>();
match state {
MyWidgetState::Dragging { .. } => mouse::Interaction::Grabbing,
MyWidgetState::Hover => mouse::Interaction::Pointer,
_ => mouse::Interaction::default(),
}
}
fn tag(&self) -> tree::Tag {
tree::Tag::of::<MyWidgetState>()
}
fn state(&self) -> tree::State {
tree::State::new(MyWidgetState::default())
}
}
impl<'a> From<MyWidget<'a>> for Element<'a> {
fn from(widget: MyWidget<'a>) -> Self {
Element::new(widget)
}
}
shell.request_redraw() when state changestree.state.downcast_mut()layout.bounds()if let Some(position) = cursor.position()theme.extended_palette() correctlyiced_core::Renderer as _Renderer