use crate::gui::{Message, State};
use crate::theme;
use aethermap_common::Action;
use iced::{
widget::{
button, checkbox, column, container, row, scrollable, slider, text, text_input, Column,
Space,
},
Alignment, Element, Length,
};
fn format_action_with_icon(action: &Action) -> String {
match action {
Action::KeyPress(key) => format!("⌨️ Press Key {}", key),
Action::KeyRelease(key) => format!("⌨️ Release Key {}", key),
Action::Delay(ms) => format!("⏱️ Wait {}ms", ms),
Action::MousePress(btn) => format!("🖱️ Click Button {}", btn),
Action::MouseRelease(btn) => format!("🖱️ Release Button {}", btn),
Action::MouseMove(x, y) => format!("↕️ Move X={} Y={}", x, y),
Action::MouseScroll(amount) => format!("🔄 Scroll {}", amount),
Action::Execute(cmd) => format!("▶️ Execute {}", cmd),
Action::Type(text) => format!("⌨️ Type {}", text),
Action::AnalogMove {
axis_code,
normalized,
} => {
let axis_name = match axis_code {
61000 => "X",
61001 => "Y",
61002 => "Z",
61003 => "RX",
61004 => "RY",
61005 => "RZ",
_ => "UNKNOWN",
};
format!("🕹️ Analog({}, {:.2})", axis_name, normalized)
}
}
}
pub fn view(state: &State) -> Element<'_, Message> {
let header = row![
text("MACROS").size(24),
Space::with_width(Length::Fill),
text(format!("{} total", state.macros.len())).size(14),
]
.align_items(Alignment::Center);
let recording_section = view_recording_panel(state);
let settings_section = view_macro_settings_panel(state);
let macro_list = view_macro_list(state);
column![
header,
Space::with_height(20),
row![recording_section, settings_section,].spacing(20),
Space::with_height(20),
text("MACRO LIBRARY").size(18),
Space::with_height(10),
macro_list,
]
.spacing(10)
.into()
}
fn view_recording_panel(state: &State) -> Element<'_, Message> {
let name_input = text_input(
"Enter macro name (e.g., 'Quick Reload')",
&state.new_macro_name,
)
.on_input(Message::UpdateMacroName)
.padding(12)
.size(14);
let record_button = if state.recording {
let indicator = if state.recording_pulse { "●" } else { "○" };
button(
row![
text(indicator).size(18),
Space::with_width(8),
text("STOP RECORDING").size(14),
]
.align_items(Alignment::Center),
)
.on_press(Message::StopRecording)
.style(iced::theme::Button::Destructive)
.padding([14, 24])
} else {
button(
row![
text("⏺").size(18),
Space::with_width(8),
text("START RECORDING").size(14),
]
.align_items(Alignment::Center),
)
.on_press(Message::StartRecording)
.style(iced::theme::Button::Primary)
.padding([14, 24])
};
let instructions = column![
text("Recording Instructions").size(14),
Space::with_height(8),
text("1. Go to Devices tab and grab a device").size(12),
text("2. Enter a descriptive macro name above").size(12),
text("3. Click 'Start Recording' and press keys").size(12),
text("4. Click 'Stop Recording' when finished").size(12),
]
.spacing(4);
let recording_status = if state.recording {
container(
row![
text("●").size(14),
Space::with_width(8),
text(format!(
"Recording '{}' - Press keys on grabbed device...",
state.recording_macro_name.as_deref().unwrap_or("")
))
.size(13),
]
.align_items(Alignment::Center),
)
.padding(12)
.width(Length::Fill)
.style(theme::styles::card)
} else {
container(text(""))
};
let panel_content = column![
text("MACRO RECORDING").size(16),
Space::with_height(16),
name_input,
Space::with_height(16),
instructions,
Space::with_height(16),
recording_status,
Space::with_height(16),
container(record_button).center_x(),
];
container(panel_content)
.padding(20)
.width(Length::Fill)
.style(theme::styles::card)
.into()
}
fn view_macro_settings_panel(state: &State) -> Element<'_, Message> {
let latency_label = text(format!(
"Latency Offset: {}ms",
state.macro_settings.latency_offset_ms
))
.size(14);
let latency_slider = slider(
0..=200,
state.macro_settings.latency_offset_ms,
Message::LatencyChanged,
);
let jitter_label = text(format!(
"Jitter: {:.0}%",
state.macro_settings.jitter_pct * 100.0
))
.size(14);
let jitter_slider = slider(
0.0..=0.5,
state.macro_settings.jitter_pct,
Message::JitterChanged,
)
.step(0.01);
let capture_mouse_checkbox = checkbox(
"Capture Mouse (Macro playback moves mouse)",
state.macro_settings.capture_mouse,
)
.on_toggle(Message::CaptureMouseToggled)
.size(14);
let content = column![
text("GLOBAL MACRO SETTINGS").size(16),
Space::with_height(16),
latency_label,
latency_slider,
Space::with_height(12),
jitter_label,
jitter_slider,
Space::with_height(16),
capture_mouse_checkbox,
]
.spacing(4);
container(content)
.padding(20)
.width(Length::Fill)
.style(theme::styles::card)
.into()
}
fn view_macro_action(action: &Action) -> Element<'_, Message> {
let action_text = format_action_with_icon(action);
text(action_text).size(11).into()
}
fn view_macro_list(state: &State) -> Element<'_, Message> {
if state.macros.is_empty() {
return container(
column![
text("No macros yet").size(14),
text("Record your first macro above").size(12),
]
.spacing(8)
.align_items(Alignment::Center),
)
.padding(20)
.width(Length::Fill)
.center_x()
.into();
}
let mut list: Column<Message> = column![].spacing(8);
for macro_entry in &state.macros {
let is_recent = state
.recently_updated_macros
.contains_key(¯o_entry.name);
let name_prefix = if is_recent { "★ " } else { "⚡ " };
let action_preview: Vec<Element<'_, Message>> = macro_entry
.actions
.iter()
.take(3)
.map(|action| view_macro_action(action))
.collect();
let more_indicator = if macro_entry.actions.len() > 3 {
Some(
text(format!(
"+ {} more actions...",
macro_entry.actions.len() - 3
))
.size(10),
)
} else {
None
};
let macro_card = container(
row![
column![
text(format!("{}{}", name_prefix, macro_entry.name)).size(15),
text(format!(
"{} actions | {} trigger keys | {}",
macro_entry.actions.len(),
macro_entry.trigger.keys.len(),
if macro_entry.enabled {
"enabled"
} else {
"disabled"
}
))
.size(11),
column(action_preview).spacing(2).padding([4, 0]),
more_indicator.unwrap_or_else(|| text("").size(10)),
]
.spacing(4),
Space::with_width(Length::Fill),
button("▶ Test")
.on_press(Message::PlayMacro(macro_entry.name.clone()))
.style(iced::theme::Button::Secondary),
button("🗑")
.on_press(Message::DeleteMacro(macro_entry.name.clone()))
.style(iced::theme::Button::Destructive),
]
.spacing(8)
.align_items(Alignment::Center),
)
.padding(12)
.width(Length::Fill)
.style(theme::styles::card);
list = list.push(macro_card);
}
scrollable(list).height(300).into()
}