use elicitation::{Prop, VerifiedWorkflow, elicit_tool};
use elicitation_derive::ElicitPlugin;
use rmcp::{ErrorData, model::CallToolResult, model::Content};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tracing::instrument;
#[derive(Prop)]
pub struct WinitEventLoopScaffolded;
impl VerifiedWorkflow for WinitEventLoopScaffolded {}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct AppSkeletonParams {
pub app_name: String,
pub title: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct EventLoopParams {
pub app_var: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ResumedHandlerParams {
pub title: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct WindowEventDispatchParams {}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct AboutToWaitParams {}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CloseRequestedParams {}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ResizedParams {}
fn text(s: String) -> Result<CallToolResult, ErrorData> {
Ok(CallToolResult::success(vec![Content::text(s)]))
}
#[elicit_tool(
plugin = "winit_event",
name = "winit_event__app_skeleton",
description = "Generate a complete `ApplicationHandler` impl skeleton for a named application struct. \
Includes field declarations, `new()`, and all required trait methods."
)]
#[instrument]
async fn event_app_skeleton(p: AppSkeletonParams) -> Result<CallToolResult, ErrorData> {
let name = &p.app_name;
let title = &p.title;
let code = format!(
r#"use std::sync::Arc;
use winit::{{
application::ApplicationHandler,
event::{{Event, WindowEvent}},
event_loop::{{ActiveEventLoop, ControlFlow, EventLoop}},
window::{{Window, WindowAttributes}},
}};
pub struct {name} {{
window: Option<Arc<Window>>,
}}
impl {name} {{
pub fn new() -> Self {{
Self {{ window: None }}
}}
}}
impl ApplicationHandler for {name} {{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {{
let attrs = WindowAttributes::default().with_title({title:?});
self.window = Some(Arc::new(
event_loop.create_window(attrs).expect("Failed to create window"),
));
}}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: winit::window::WindowId,
event: WindowEvent,
) {{
match event {{
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {{
// TODO: render here
}}
_ => {{}}
}}
}}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {{
if let Some(w) = &self.window {{
w.request_redraw();
}}
}}
}}"#
);
text(code)
}
#[elicit_tool(
plugin = "winit_event",
name = "winit_event__event_loop",
description = "Generate the `main` function body that creates an `EventLoop` and runs an application."
)]
#[instrument]
async fn event_event_loop(p: EventLoopParams) -> Result<CallToolResult, ErrorData> {
let var = &p.app_var;
let code = format!(
r#"fn main() -> Result<(), impl std::error::Error> {{
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
let mut {var} = App::new();
event_loop.run_app(&mut {var})
}}"#
);
text(code)
}
#[elicit_tool(
plugin = "winit_event",
name = "winit_event__resumed_handler",
description = "Generate just the `fn resumed` method body for `ApplicationHandler`, \
creating a window with the given title."
)]
#[instrument]
async fn event_resumed_handler(p: ResumedHandlerParams) -> Result<CallToolResult, ErrorData> {
let title = &p.title;
let code = format!(
r#"fn resumed(&mut self, event_loop: &ActiveEventLoop) {{
let attrs = WindowAttributes::default().with_title({title:?});
self.window = Some(Arc::new(
event_loop.create_window(attrs).expect("Failed to create window"),
));
}}"#
);
text(code)
}
#[elicit_tool(
plugin = "winit_event",
name = "winit_event__window_event_dispatch",
description = "Generate a `fn window_event` method skeleton with the most common `WindowEvent` \
match arms (CloseRequested, Resized, RedrawRequested)."
)]
#[instrument]
async fn event_window_event_dispatch(
_p: WindowEventDispatchParams,
) -> Result<CallToolResult, ErrorData> {
text(
r#"fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: winit::window::WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::Resized(size) => {
// TODO: handle resize — update viewport / surface
let _ = size;
}
WindowEvent::RedrawRequested => {
// TODO: render
}
_ => {}
}
}"#
.to_string(),
)
}
#[elicit_tool(
plugin = "winit_event",
name = "winit_event__about_to_wait",
description = "Generate a `fn about_to_wait` body that calls `request_redraw` \
to drive a continuous render loop."
)]
#[instrument]
async fn event_about_to_wait(_p: AboutToWaitParams) -> Result<CallToolResult, ErrorData> {
text(
r#"fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(w) = &self.window {
w.request_redraw();
}
}"#
.to_string(),
)
}
#[elicit_tool(
plugin = "winit_event",
name = "winit_event__close_requested",
description = "Generate a `WindowEvent::CloseRequested` match arm that exits the event loop."
)]
#[instrument]
async fn event_close_requested(_p: CloseRequestedParams) -> Result<CallToolResult, ErrorData> {
text("WindowEvent::CloseRequested => event_loop.exit(),".to_string())
}
#[elicit_tool(
plugin = "winit_event",
name = "winit_event__resized",
description = "Generate a `WindowEvent::Resized(size)` match arm with a TODO placeholder \
for updating the rendering surface."
)]
#[instrument]
async fn event_resized(_p: ResizedParams) -> Result<CallToolResult, ErrorData> {
text(
r#"WindowEvent::Resized(size) => {
// TODO: update surface / viewport to new size
let _ = size;
}"#
.to_string(),
)
}
#[derive(Debug, ElicitPlugin)]
#[plugin(name = "winit_event")]
pub struct WinitEventPlugin;
impl WinitEventPlugin {
#[instrument]
pub fn new() -> Self {
Self
}
pub async fn invoke_tool(
&self,
name: &str,
args: serde_json::Value,
) -> Result<rmcp::model::CallToolResult, rmcp::ErrorData> {
use elicitation::{NoContext, PluginToolRegistration, inventory};
let found = inventory::iter::<PluginToolRegistration>()
.filter(|r| r.plugin == "winit_event")
.find(|r| r.name == name)
.map(|r| (r.constructor)());
let params = if let Some(m) = args.as_object().cloned() {
rmcp::model::CallToolRequestParams::new(name.to_string()).with_arguments(m)
} else {
rmcp::model::CallToolRequestParams::new(name.to_string())
};
match found {
Some(descriptor) => {
descriptor
.dispatch(std::sync::Arc::new(NoContext), params)
.await
}
None => Err(rmcp::ErrorData::invalid_params(
format!("unknown tool: {name}"),
None,
)),
}
}
}
impl Default for WinitEventPlugin {
fn default() -> Self {
Self::new()
}
}