use elicitation::ToCodeLiteral;
use elicitation::elicit_tool;
use rmcp::ErrorData;
use rmcp::model::{CallToolResult, Content};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tracing::instrument;
use crate::serde_types::{ColorJson, CornerRadiusJson, MarginJson, StrokeJson, Vec2Json};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, ToCodeLiteral)]
#[serde(tag = "type")]
pub enum StyleJson {
Spacing {
#[serde(default, skip_serializing_if = "Option::is_none")]
item_spacing: Option<Vec2Json>,
#[serde(default, skip_serializing_if = "Option::is_none")]
window_margin: Option<Vec2Json>,
#[serde(default, skip_serializing_if = "Option::is_none")]
button_padding: Option<Vec2Json>,
#[serde(default, skip_serializing_if = "Option::is_none")]
indent: Option<f32>,
},
DarkMode,
LightMode,
Visual {
property: VisualProperty,
color: ColorJson,
},
WindowRounding {
corner_radius: CornerRadiusJson,
},
WindowShadow {
offset_x: f32,
offset_y: f32,
blur: f32,
color: ColorJson,
},
WidgetVisuals {
state: WidgetState,
#[serde(default, skip_serializing_if = "Option::is_none")]
bg_fill: Option<ColorJson>,
#[serde(default, skip_serializing_if = "Option::is_none")]
weak_bg_fill: Option<ColorJson>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bg_stroke: Option<StrokeJson>,
#[serde(default, skip_serializing_if = "Option::is_none")]
corner_radius: Option<CornerRadiusJson>,
#[serde(default, skip_serializing_if = "Option::is_none")]
fg_stroke: Option<StrokeJson>,
},
SelectionColor {
bg_fill: ColorJson,
stroke: StrokeJson,
},
TextCursor {
#[serde(default, skip_serializing_if = "Option::is_none")]
color: Option<ColorJson>,
#[serde(default, skip_serializing_if = "Option::is_none")]
width: Option<f32>,
},
SetFonts {
family: FontFamily,
font_names: Vec<String>,
},
OverrideTextStyle {
style: TextStyleName,
family: FontFamily,
size: f32,
},
SetTextValign {
valign: TextValign,
},
Interaction {
#[serde(default, skip_serializing_if = "Option::is_none")]
sense_click_time: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
sense_drag_threshold: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
tooltip_delay: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
show_tooltips_only_when_still: Option<bool>,
},
AnimationTime {
duration: f32,
},
DebugOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
show_widget_hits: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
debug_on_hover: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
show_resize: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
show_interactive_widgets: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
show_blocking_widget: Option<bool>,
},
WindowStroke {
stroke: StrokeJson,
},
MenuMargin {
margin: MarginJson,
},
ScrollBar {
#[serde(default, skip_serializing_if = "Option::is_none")]
bar_width: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
handle_min_length: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bar_inner_margin: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bar_outer_margin: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
floating: Option<bool>,
},
ResizeGripSize {
size: f32,
},
TextCursorBlink {
#[serde(default, skip_serializing_if = "Option::is_none")]
width: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
blink_on: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
blink_off: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
preview: Option<bool>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToCodeLiteral)]
pub enum VisualProperty {
HyperlinkColor,
FaintBgColor,
ExtremeBgColor,
CodeBgColor,
WarnFgColor,
ErrorFgColor,
WindowFill,
PanelFill,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToCodeLiteral)]
pub enum WidgetState {
Noninteractive,
Inactive,
Hovered,
Active,
Open,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToCodeLiteral)]
pub enum FontFamily {
Proportional,
Monospace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToCodeLiteral)]
pub enum TextStyleName {
Heading,
Body,
Monospace,
Button,
Small,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToCodeLiteral)]
pub enum TextValign {
Top,
Center,
Bottom,
}
fn style_result(style: &StyleJson) -> CallToolResult {
match serde_json::to_string(style) {
Ok(s) => CallToolResult::success(vec![Content::text(s)]),
Err(e) => CallToolResult::error(vec![Content::text(format!("serialize error: {e}"))]),
}
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct EmptyStyleParams {}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct SpacingParams {
pub item_spacing: Option<Vec2Json>,
pub window_margin: Option<Vec2Json>,
pub button_padding: Option<Vec2Json>,
pub indent: Option<f32>,
}
#[elicit_tool(
plugin = "egui_style",
name = "style_spacing",
description = "Set global spacing values (item spacing, margins, padding). Returns StyleJson::Spacing."
)]
#[instrument(skip_all)]
async fn style_spacing(p: SpacingParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Spacing {
item_spacing: p.item_spacing,
window_margin: p.window_margin,
button_padding: p.button_padding,
indent: p.indent,
};
Ok(style_result(&s))
}
#[elicit_tool(
plugin = "egui_style",
name = "style_dark_mode",
description = "Switch to dark visual theme. Returns StyleJson::DarkMode."
)]
#[instrument(skip_all)]
async fn style_dark_mode(p: EmptyStyleParams) -> Result<CallToolResult, ErrorData> {
let _ = p;
Ok(style_result(&StyleJson::DarkMode))
}
#[elicit_tool(
plugin = "egui_style",
name = "style_light_mode",
description = "Switch to light visual theme. Returns StyleJson::LightMode.",
emit = None
)]
#[instrument(skip_all)]
async fn style_light_mode(p: EmptyStyleParams) -> Result<CallToolResult, ErrorData> {
let _ = p;
Ok(style_result(&StyleJson::LightMode))
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct VisualParams {
pub property: VisualProperty,
pub color: ColorJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "style_visual",
description = "Set a visual colour property (hyperlink, background, etc.). Returns StyleJson::Visual."
)]
#[instrument(skip_all)]
async fn style_visual(p: VisualParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Visual {
property: p.property,
color: p.color,
};
Ok(style_result(&s))
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct WindowRoundingParams {
pub corner_radius: CornerRadiusJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "style_window_rounding",
description = "Set window corner rounding. Returns StyleJson::WindowRounding."
)]
#[instrument(skip_all)]
async fn style_window_rounding(p: WindowRoundingParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::WindowRounding {
corner_radius: p.corner_radius,
};
Ok(style_result(&s))
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct WindowShadowParams {
pub offset_x: f32,
pub offset_y: f32,
pub blur: f32,
pub color: ColorJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "style_window_shadow",
description = "Set window drop shadow. Returns StyleJson::WindowShadow."
)]
#[instrument(skip_all)]
async fn style_window_shadow(p: WindowShadowParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::WindowShadow {
offset_x: p.offset_x,
offset_y: p.offset_y,
blur: p.blur,
color: p.color,
};
Ok(style_result(&s))
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct WidgetVisualsParams {
pub state: WidgetState,
pub bg_fill: Option<ColorJson>,
pub weak_bg_fill: Option<ColorJson>,
pub bg_stroke: Option<StrokeJson>,
pub corner_radius: Option<CornerRadiusJson>,
pub fg_stroke: Option<StrokeJson>,
}
#[elicit_tool(
plugin = "egui_style",
name = "style_widget_visuals",
description = "Override widget visuals (fill, stroke, rounding) for a state. Returns StyleJson::WidgetVisuals."
)]
#[instrument(skip_all)]
async fn style_widget_visuals(p: WidgetVisualsParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::WidgetVisuals {
state: p.state,
bg_fill: p.bg_fill,
weak_bg_fill: p.weak_bg_fill,
bg_stroke: p.bg_stroke,
corner_radius: p.corner_radius,
fg_stroke: p.fg_stroke,
};
Ok(style_result(&s))
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct SelectionParams {
pub bg_fill: ColorJson,
pub stroke: StrokeJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "style_selection",
description = "Set selection highlight colours. Returns StyleJson::SelectionColor."
)]
#[instrument(skip_all)]
async fn style_selection(p: SelectionParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::SelectionColor {
bg_fill: p.bg_fill,
stroke: p.stroke,
};
Ok(style_result(&s))
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct TextCursorParams {
pub color: Option<ColorJson>,
pub width: Option<f32>,
}
#[elicit_tool(
plugin = "egui_style",
name = "style_text_cursor",
description = "Set text cursor colour and width. Returns StyleJson::TextCursor."
)]
#[instrument(skip_all)]
async fn style_text_cursor(p: TextCursorParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::TextCursor {
color: p.color,
width: p.width,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct SetFontsParams {
pub family: FontFamily,
pub font_names: Vec<String>,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_fonts",
description = "Configure font families (proportional/monospace) with font data names. Returns StyleJson::SetFonts."
)]
#[instrument(skip_all)]
async fn egui_set_fonts(p: SetFontsParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::SetFonts {
family: p.family,
font_names: p.font_names,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct OverrideTextStyleParams {
pub style: TextStyleName,
pub family: FontFamily,
pub size: f32,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_override_text_style",
description = "Override a named text style (Heading, Body, Monospace, Button, Small) with font family and size. Returns StyleJson::OverrideTextStyle."
)]
#[instrument(skip_all)]
async fn egui_override_text_style(p: OverrideTextStyleParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::OverrideTextStyle {
style: p.style,
family: p.family,
size: p.size,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct SetTextValignParams {
pub valign: TextValign,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_text_valign",
description = "Set vertical text alignment (Top, Center, Bottom). Returns StyleJson::SetTextValign."
)]
#[instrument(skip_all)]
async fn egui_set_text_valign(p: SetTextValignParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::SetTextValign { valign: p.valign };
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct InteractionParams {
pub sense_click_time: Option<f32>,
pub sense_drag_threshold: Option<f32>,
pub tooltip_delay: Option<f32>,
pub show_tooltips_only_when_still: Option<bool>,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_interaction",
description = "Configure interaction settings (click time, drag threshold, tooltip delay). Returns StyleJson::Interaction."
)]
#[instrument(skip_all)]
async fn egui_set_interaction(p: InteractionParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Interaction {
sense_click_time: p.sense_click_time,
sense_drag_threshold: p.sense_drag_threshold,
tooltip_delay: p.tooltip_delay,
show_tooltips_only_when_still: p.show_tooltips_only_when_still,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct AnimationTimeParams {
pub duration: f32,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_animation_time",
description = "Set animation duration for UI transitions (seconds). Returns StyleJson::AnimationTime."
)]
#[instrument(skip_all)]
async fn egui_set_animation_time(p: AnimationTimeParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::AnimationTime {
duration: p.duration,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct DebugOptionsParams {
pub show_widget_hits: Option<bool>,
pub debug_on_hover: Option<bool>,
pub show_resize: Option<bool>,
pub show_interactive_widgets: Option<bool>,
pub show_blocking_widget: Option<bool>,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_debug_options",
description = "Toggle debug rendering (widget hits, debug on hover, resize indicators). Returns StyleJson::DebugOptions."
)]
#[instrument(skip_all)]
async fn egui_set_debug_options(p: DebugOptionsParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::DebugOptions {
show_widget_hits: p.show_widget_hits,
debug_on_hover: p.debug_on_hover,
show_resize: p.show_resize,
show_interactive_widgets: p.show_interactive_widgets,
show_blocking_widget: p.show_blocking_widget,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ColorOverrideParams {
pub color: ColorJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_hyperlink_color",
description = "Set hyperlink colour. Returns StyleJson::Visual with HyperlinkColor."
)]
#[instrument(skip_all)]
async fn egui_set_hyperlink_color(p: ColorOverrideParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Visual {
property: VisualProperty::HyperlinkColor,
color: p.color,
};
Ok(style_result(&s))
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_faint_bg_color",
description = "Set faint background colour for alternating rows/subtle backgrounds. Returns StyleJson::Visual with FaintBgColor.",
emit = None
)]
#[instrument(skip_all)]
async fn egui_set_faint_bg_color(p: ColorOverrideParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Visual {
property: VisualProperty::FaintBgColor,
color: p.color,
};
Ok(style_result(&s))
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_extreme_bg_color",
description = "Set extreme background colour (e.g. text input fields). Returns StyleJson::Visual with ExtremeBgColor.",
emit = None
)]
#[instrument(skip_all)]
async fn egui_set_extreme_bg_color(p: ColorOverrideParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Visual {
property: VisualProperty::ExtremeBgColor,
color: p.color,
};
Ok(style_result(&s))
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_code_bg_color",
description = "Set code/monospace background colour. Returns StyleJson::Visual with CodeBgColor.",
emit = None
)]
#[instrument(skip_all)]
async fn egui_set_code_bg_color(p: ColorOverrideParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Visual {
property: VisualProperty::CodeBgColor,
color: p.color,
};
Ok(style_result(&s))
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_warn_fg_color",
description = "Set warning foreground colour. Returns StyleJson::Visual with WarnFgColor.",
emit = None
)]
#[instrument(skip_all)]
async fn egui_set_warn_fg_color(p: ColorOverrideParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Visual {
property: VisualProperty::WarnFgColor,
color: p.color,
};
Ok(style_result(&s))
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_error_fg_color",
description = "Set error foreground colour. Returns StyleJson::Visual with ErrorFgColor.",
emit = None
)]
#[instrument(skip_all)]
async fn egui_set_error_fg_color(p: ColorOverrideParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Visual {
property: VisualProperty::ErrorFgColor,
color: p.color,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct WidgetStrokeParams {
pub state: WidgetState,
pub stroke: StrokeJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_widget_stroke",
description = "Set border stroke (width + colour) for a widget state (Inactive, Hovered, Active, Open). Returns StyleJson::WidgetVisuals."
)]
#[instrument(skip_all)]
async fn egui_set_widget_stroke(p: WidgetStrokeParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::WidgetVisuals {
state: p.state,
bg_fill: None,
weak_bg_fill: None,
bg_stroke: Some(p.stroke),
corner_radius: None,
fg_stroke: None,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct WindowStrokeParams {
pub stroke: StrokeJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_window_stroke",
description = "Set window border stroke (width + colour). Returns StyleJson::WindowStroke."
)]
#[instrument(skip_all)]
async fn egui_set_window_stroke(p: WindowStrokeParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::WindowStroke { stroke: p.stroke };
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct MenuMarginParams {
pub margin: MarginJson,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_menu_margin",
description = "Set margin around menus. Returns StyleJson::MenuMargin."
)]
#[instrument(skip_all)]
async fn egui_set_menu_margin(p: MenuMarginParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::MenuMargin { margin: p.margin };
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ButtonPaddingParams {
pub padding: Vec2Json,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_button_padding",
description = "Set button padding (horizontal, vertical). Returns StyleJson::Spacing."
)]
#[instrument(skip_all)]
async fn egui_set_button_padding(p: ButtonPaddingParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Spacing {
item_spacing: None,
window_margin: None,
button_padding: Some(p.padding),
indent: None,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct StyleIndentParams {
pub indent: f32,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_indent",
description = "Set indentation distance in logical pixels. Returns StyleJson::Spacing."
)]
#[instrument(skip_all)]
async fn egui_set_indent(p: StyleIndentParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::Spacing {
item_spacing: None,
window_margin: None,
button_padding: None,
indent: Some(p.indent),
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ScrollBarWidthParams {
pub bar_width: Option<f32>,
pub handle_min_length: Option<f32>,
pub bar_inner_margin: Option<f32>,
pub bar_outer_margin: Option<f32>,
pub floating: Option<bool>,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_scroll_bar_width",
description = "Set scroll bar width, handle length, margins, and floating mode. Returns StyleJson::ScrollBar."
)]
#[instrument(skip_all)]
async fn egui_set_scroll_bar_width(p: ScrollBarWidthParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::ScrollBar {
bar_width: p.bar_width,
handle_min_length: p.handle_min_length,
bar_inner_margin: p.bar_inner_margin,
bar_outer_margin: p.bar_outer_margin,
floating: p.floating,
};
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ResizeGripSizeParams {
pub size: f32,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_resize_grip_size",
description = "Set resize grip/handle corner size. Returns StyleJson::ResizeGripSize."
)]
#[instrument(skip_all)]
async fn egui_set_resize_grip_size(p: ResizeGripSizeParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::ResizeGripSize { size: p.size };
Ok(style_result(&s))
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct TextCursorBlinkParams {
pub width: Option<f32>,
pub blink_on: Option<f32>,
pub blink_off: Option<f32>,
pub preview: Option<bool>,
}
#[elicit_tool(
plugin = "egui_style",
name = "egui_set_text_cursor_width",
description = "Set text cursor blink settings (width, blink on/off durations, preview). Returns StyleJson::TextCursorBlink."
)]
#[instrument(skip_all)]
async fn egui_set_text_cursor_width(p: TextCursorBlinkParams) -> Result<CallToolResult, ErrorData> {
let s = StyleJson::TextCursorBlink {
width: p.width,
blink_on: p.blink_on,
blink_off: p.blink_off,
preview: p.preview,
};
Ok(style_result(&s))
}