modde_ui/views/
settings.rs1use std::path::PathBuf;
2
3use iced::widget::{button, column, pick_list, row, scrollable, text, text_input};
4use iced::{color, Alignment, Element, Length};
5
6use crate::app::{Message, NexusAuthStatus, SettingsState};
7
8pub fn view(state: SettingsState) -> Element<'static, Message> {
10 let title = text("Settings").size(20);
11
12 let nexus_status_widget: Element<'static, Message> = match &state.nexus_status {
14 Some(NexusAuthStatus::Checking) => text("Checking...").size(12).color(color!(0xAAAA44)).into(),
15 Some(NexusAuthStatus::Valid { username, is_premium }) => {
16 let tier = if *is_premium { "Premium" } else { "Standard" };
17 text(format!("Logged in as {username} ({tier})"))
18 .size(12)
19 .color(color!(0x88CC88))
20 .into()
21 }
22 Some(NexusAuthStatus::Invalid(err)) => {
23 text(format!("Invalid: {err}"))
24 .size(12)
25 .color(color!(0xFF4444))
26 .into()
27 }
28 None => text("Not validated").size(12).into(),
29 };
30
31 let game_path_str = state.game_path.as_ref().map(|p| p.display().to_string()).unwrap_or_default();
32 let download_dir_str = state.download_dir.as_ref().map(|p| p.display().to_string()).unwrap_or_default();
33
34 let api_key_section = column![
35 text("Nexus Mods API Key").size(14),
36 text("Required for downloading mods and browsing collections.").size(11),
37 row![
38 text_input("Enter your API key...", &state.nexus_api_key)
39 .on_input(Message::SetNexusApiKey)
40 .padding(8)
41 .width(Length::Fill),
42 button(text("Validate").size(13))
43 .on_press(Message::ValidateNexusKey)
44 .style(button::primary)
45 .padding([6, 12]),
46 ]
47 .spacing(8)
48 .align_y(Alignment::Center),
49 nexus_status_widget,
50 ]
51 .spacing(4);
52
53 let game_path_section = column![
55 text("Game Install Path").size(14),
56 text("Root directory of the game installation.").size(11),
57 row![
58 text_input(
59 "/path/to/game",
60 &game_path_str,
61 )
62 .on_input(|s| Message::SetGamePath {
63 game_id: "default".to_string(),
64 path: PathBuf::from(s),
65 })
66 .padding(8)
67 .width(Length::Fill),
68 button(text("Browse").size(13))
69 .on_press(Message::BrowseGamePath)
70 .style(button::secondary)
71 .padding([6, 12]),
72 ]
73 .spacing(8)
74 .align_y(Alignment::Center),
75 ]
76 .spacing(4);
77
78 let download_dir_section = column![
80 text("Download Directory").size(14),
81 text("Where downloaded mod archives are stored.").size(11),
82 row![
83 text_input(
84 "/path/to/downloads",
85 &download_dir_str,
86 )
87 .on_input(|s| Message::SetDownloadDir(PathBuf::from(s)))
88 .padding(8)
89 .width(Length::Fill),
90 button(text("Browse").size(13))
91 .on_press(Message::BrowseDownloadDir)
92 .style(button::secondary)
93 .padding([6, 12]),
94 ]
95 .spacing(8)
96 .align_y(Alignment::Center),
97 ]
98 .spacing(4);
99
100 let stock_game_section = column![
102 text("Stock Game Snapshot").size(14),
103 text("Create a snapshot of your clean game install for virtual deployment.").size(11),
104 row![
105 button(text("Create Snapshot").size(13))
106 .on_press(Message::CreateStockSnapshot)
107 .style(button::primary)
108 .padding([6, 14]),
109 button(text("Verify Snapshot").size(13))
110 .on_press(Message::VerifyStockSnapshot)
111 .style(button::secondary)
112 .padding([6, 14]),
113 text(if state.has_stock_snapshot {
114 "Snapshot exists"
115 } else {
116 "No snapshot created"
117 })
118 .size(12),
119 ]
120 .spacing(12)
121 .align_y(Alignment::Center),
122 ]
123 .spacing(4);
124
125 let theme_options = vec![
127 "Dark".to_string(),
128 "Light".to_string(),
129 "Dracula".to_string(),
130 "Nord".to_string(),
131 "Gruvbox Dark".to_string(),
132 "Catppuccin Mocha".to_string(),
133 ];
134 let theme_section = column![
135 text("Theme").size(14),
136 pick_list(
137 theme_options,
138 Some(state.theme_name.clone()),
139 Message::SetTheme,
140 )
141 .width(Length::Fixed(200.0)),
142 ]
143 .spacing(4);
144
145 let content = scrollable(
146 column![
147 api_key_section,
148 iced::widget::rule::horizontal(1),
149 game_path_section,
150 iced::widget::rule::horizontal(1),
151 download_dir_section,
152 iced::widget::rule::horizontal(1),
153 stock_game_section,
154 iced::widget::rule::horizontal(1),
155 theme_section,
156 ]
157 .spacing(16)
158 .padding(16),
159 )
160 .height(Length::Fill);
161
162 column![title, iced::widget::rule::horizontal(1), content,]
163 .spacing(8)
164 .padding(16)
165 .width(Length::Fill)
166 .height(Length::Fill)
167 .into()
168}