Skip to main content

modde_ui/views/
tools.rs

1use iced::widget::{button, column, container, row, scrollable, text, toggler};
2use iced::{color, Alignment, Element, Length};
3
4use crate::app::{Message, ToolState, ToolUiEntry};
5
6/// Render the gaming tools/overlays management view.
7pub fn view<'a>(state: &'a ToolState) -> Element<'a, Message> {
8    let title_bar = row![
9        text("Gaming Tools").size(20),
10        iced::widget::space::horizontal(),
11        button(text("Refresh").size(14))
12            .on_press(Message::RefreshTools)
13            .style(button::secondary)
14            .padding([6, 14]),
15    ]
16    .align_y(Alignment::Center);
17
18    let content: Element<'_, Message> = if state.entries.is_empty() {
19        container(
20            text("Select a game to manage tools, or click Refresh.").size(14),
21        )
22        .padding(20)
23        .width(Length::Fill)
24        .center_x(Length::Fill)
25        .into()
26    } else {
27        let cards = state.entries.iter().fold(column![].spacing(8), |col: iced::widget::Column<'_, Message>, entry| {
28            col.push(tool_card(entry))
29        });
30
31        scrollable(
32            container(cards)
33                .padding(12)
34                .width(Length::Fill),
35        )
36        .into()
37    };
38
39    column![title_bar, content]
40        .spacing(12)
41        .padding(12)
42        .width(Length::Fill)
43        .into()
44}
45
46/// Render a single tool card.
47fn tool_card(entry: &ToolUiEntry) -> Element<'_, Message> {
48    let tool_id = entry.tool_id.clone();
49    let available = entry.available;
50
51    // Header row: name + category + toggle
52    let name_text = text(entry.display_name.as_str()).size(16);
53    let category_text = text(entry.category.as_str())
54        .size(11)
55        .color(color!(0x888888));
56
57    let avail_text = if available {
58        text("installed").size(11).color(color!(0x88CC88))
59    } else {
60        text("not installed").size(11).color(color!(0xFF6666))
61    };
62
63    let toggle = toggler(entry.enabled)
64        .on_toggle({
65            let tid = tool_id.clone();
66            move |enabled| Message::ToggleTool {
67                tool_id: tid.clone(),
68                enabled,
69            }
70        })
71        .size(18.0);
72
73    let header = row![
74        column![name_text, category_text].spacing(2),
75        iced::widget::space::horizontal(),
76        avail_text,
77        toggle,
78    ]
79    .align_y(Alignment::Center)
80    .spacing(12);
81
82    // Applied files count
83    let mut body = column![].spacing(4);
84
85    if entry.applied_files > 0 {
86        body = body.push(
87            text(format!("{} file(s) applied to game directory", entry.applied_files))
88                .size(12)
89                .color(color!(0xAAAA66)),
90        );
91    }
92
93    // Action buttons (for patching tools)
94    if entry.has_file_patching {
95        let tid_apply = tool_id.clone();
96        let tid_revert = tool_id.clone();
97
98        let actions = row![
99            button(text("Apply").size(12))
100                .on_press_maybe(if available {
101                    Some(Message::ApplyTool(tid_apply))
102                } else {
103                    None
104                })
105                .style(button::primary)
106                .padding([4, 10]),
107            button(text("Revert").size(12))
108                .on_press_maybe(if entry.applied_files > 0 {
109                    Some(Message::RevertTool(tid_revert))
110                } else {
111                    None
112                })
113                .style(button::danger)
114                .padding([4, 10]),
115        ]
116        .spacing(6);
117
118        body = body.push(actions);
119    }
120
121    // Status message
122    if let Some(ref msg) = entry.status_message {
123        body = body.push(
124            text(msg.as_str())
125                .size(12)
126                .color(color!(0x88CC88)),
127        );
128    }
129
130    let card = column![header, body].spacing(6);
131
132    container(card)
133        .padding(10)
134        .width(Length::Fill)
135        .style(container::rounded_box)
136        .into()
137}