Skip to content

Commit 6c7f622

Browse files
committed
fix: add last monitor toggle warning modal and fix workspace default logic
- Add warning modal when trying to disable last monitor - Fix workspace default toggle to only allow one default per monitor - Update keybinds display - Remove empty error bar when no errors
1 parent 5968c5f commit 6c7f622

5 files changed

Lines changed: 172 additions & 21 deletions

File tree

assets/xwlm.png

2.7 MB
Loading

src/state.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,11 @@ impl App {
749749
w.is_default = false;
750750
}
751751
}
752+
for w in self.workspace_assignments.iter_mut() {
753+
if w.is_default && w.monitor_idx == Some(target_monitor) {
754+
w.is_default = false;
755+
}
756+
}
752757
}
753758

754759
self.pending_workspaces.insert(ws_idx, effective);

src/tui/key_binds.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use ratatui::{
1111
widgets::Paragraph,
1212
};
1313

14-
pub fn keybinds(frame: &mut Frame, area: Rect, app: &App) {
14+
pub fn config(frame: &mut Frame, area: Rect, app: &App) {
1515
let panel = &app.panel;
1616
let mut keys = vec![
1717
Span::styled(
@@ -38,12 +38,18 @@ pub fn keybinds(frame: &mut Frame, area: Rect, app: &App) {
3838
keys.push(Span::styled("]", Style::default().fg(Color::Cyan)));
3939
}
4040
Panel::Mode => {
41-
keys.push(Span::styled("[ Modes | ", Style::default().fg(Color::Cyan)));
41+
keys.push(Span::styled(
42+
"[ Modes | ",
43+
Style::default().fg(Color::Cyan),
44+
));
4245
get_modes_keybinds(&mut keys);
4346
keys.push(Span::styled("]", Style::default().fg(Color::Cyan)));
4447
}
4548
Panel::Scale => {
46-
keys.push(Span::styled("[ Scale | ", Style::default().fg(Color::Cyan)));
49+
keys.push(Span::styled(
50+
"[ Scale | ",
51+
Style::default().fg(Color::Cyan),
52+
));
4753
get_scale_keybinds(&mut keys);
4854
keys.push(Span::styled("]", Style::default().fg(Color::Cyan)));
4955
}
@@ -93,7 +99,10 @@ pub fn get_modes_keybinds(keys: &mut Vec<Span<'static>>) {
9399
));
94100
}
95101

96-
pub fn get_workspaces_keybinds(keys: &mut Vec<Span<'static>>, compositor: Compositor) {
102+
pub fn get_workspaces_keybinds(
103+
keys: &mut Vec<Span<'static>>,
104+
compositor: Compositor,
105+
) {
97106
keys.push(Span::styled("←→ ", Style::default().fg(Color::Cyan)));
98107
keys.push(Span::styled(
99108
"assign ",

src/tui/layout.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use crate::{
22
state::App,
33
tui::{
4-
key_binds::keybinds,
5-
panels::{left, mode, workspace},
4+
key_binds,
5+
panels::{
6+
left::{self},
7+
mode, workspace,
8+
},
69
},
710
};
811

@@ -16,13 +19,26 @@ use ratatui::{
1619
pub fn draw(frame: &mut Frame, app: &mut App) {
1720
let area = frame.area();
1821

19-
let main_layout = Layout::default()
20-
.direction(Direction::Vertical)
21-
.constraints([
22+
let error_exists =
23+
app.error_message.is_some() || app.pending_last_toggle_monitor;
24+
25+
let constraints: [Constraint; 3] = if error_exists {
26+
[
2227
Constraint::Min(1),
2328
Constraint::Length(1),
2429
Constraint::Length(1),
25-
])
30+
]
31+
} else {
32+
[
33+
Constraint::Min(1),
34+
Constraint::Length(1),
35+
Constraint::Length(0),
36+
]
37+
};
38+
39+
let main_layout = Layout::default()
40+
.direction(Direction::Vertical)
41+
.constraints(constraints)
2642
.split(area);
2743

2844
let content = Layout::default()
@@ -34,13 +50,19 @@ pub fn draw(frame: &mut Frame, app: &mut App) {
3450
])
3551
.split(main_layout[0]);
3652

37-
keybinds(frame, main_layout[1], app);
3853
left::panel(frame, app, content[0]);
3954
mode::panel(frame, app, content[1]);
4055
workspace::panel(frame, app, content[2]);
56+
key_binds::config(frame, main_layout[1], app);
4157

4258
if let Some(ref err) = app.error_message {
43-
let error_bar = Paragraph::new(err.as_str()).style(Style::default().fg(Color::Red));
59+
let error_bar =
60+
Paragraph::new(err.as_str()).style(Style::default().fg(Color::Red));
4461
frame.render_widget(error_bar, main_layout[2]);
4562
}
63+
64+
if app.pending_last_toggle_monitor {
65+
let config_path = app.comp_monitor_config_path.to_string_lossy();
66+
left::render_warning_modal(frame, area, &config_path);
67+
}
4668
}

src/tui/panels/left.rs

Lines changed: 124 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::{
22
constants::TRANSFORMS,
33
state::{App, Panel},
4-
tui::key_binds::{get_monitor_keybinds, get_scale_keybinds, get_transform_keybinds},
4+
tui::key_binds::{
5+
get_monitor_keybinds, get_scale_keybinds, get_transform_keybinds,
6+
},
57
utils::{self, effective_dimensions, monitor_resolution, transform_label},
68
};
79

@@ -10,7 +12,7 @@ use ratatui::{
1012
layout::{Constraint, Direction, Layout, Rect},
1113
style::{Color, Modifier, Style},
1214
text::{Line, Span},
13-
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
15+
widgets::{Block, BorderType, Borders, Clear, List, ListItem, Paragraph},
1416
};
1517
use wlx_monitors::WlTransform;
1618

@@ -98,7 +100,10 @@ fn render_map(frame: &mut Frame, app: &App, area: Rect) {
98100
format!("{}×{} ", ew, eh),
99101
Style::default().fg(Color::White),
100102
),
101-
Span::styled(format!("({},{}) ", dx, dy), Style::default().fg(pos_color)),
103+
Span::styled(
104+
format!("({},{}) ", dx, dy),
105+
Style::default().fg(pos_color),
106+
),
102107
Span::styled(
103108
format!("{}× ", monitor.scale),
104109
Style::default().fg(Color::White),
@@ -132,9 +137,14 @@ fn render_map(frame: &mut Frame, app: &App, area: Rect) {
132137
),
133138
Span::styled(
134139
"OFF ",
135-
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
140+
Style::default()
141+
.fg(Color::Red)
142+
.add_modifier(Modifier::BOLD),
143+
),
144+
Span::styled(
145+
"— t to enable",
146+
Style::default().fg(Color::DarkGray),
136147
),
137-
Span::styled("— t to enable", Style::default().fg(Color::DarkGray)),
138148
]));
139149
}
140150
} else {
@@ -144,7 +154,11 @@ fn render_map(frame: &mut Frame, app: &App, area: Rect) {
144154
frame.render_widget(Paragraph::new(lines), inner);
145155
}
146156

147-
fn build_layout_map<'a>(app: &App, width: usize, height: usize) -> Vec<Line<'a>> {
157+
fn build_layout_map<'a>(
158+
app: &App,
159+
width: usize,
160+
height: usize,
161+
) -> Vec<Line<'a>> {
148162
let monitors = &app.monitors;
149163
let selected_idx = app.selected_monitor;
150164
let zoom = app.map_zoom;
@@ -252,7 +266,8 @@ fn build_layout_map<'a>(app: &App, width: usize, height: usize) -> Vec<Line<'a>>
252266
let cx = pad + ((rect.px - min_x) as f64 / ppc) as usize;
253267
let cy = ((rect.py - min_y) as f64 / (ppc * CHAR_ASPECT)) as usize;
254268
let cw = (rect.pw as f64 / ppc).round().max(1.0) as usize;
255-
let ch = (rect.ph as f64 / (ppc * CHAR_ASPECT)).round().max(1.0) as usize;
269+
let ch =
270+
(rect.ph as f64 / (ppc * CHAR_ASPECT)).round().max(1.0) as usize;
256271

257272
let x1 = cx.min(width.saturating_sub(1));
258273
let y1 = cy.min(height.saturating_sub(1));
@@ -344,11 +359,13 @@ fn build_layout_map<'a>(app: &App, width: usize, height: usize) -> Vec<Line<'a>>
344359
break;
345360
}
346361
let truncated: String = text.chars().take(inner_w).collect();
347-
let text_start = x1 + 1 + inner_w.saturating_sub(truncated.len()) / 2;
362+
let text_start =
363+
x1 + 1 + inner_w.saturating_sub(truncated.len()) / 2;
348364
for (j, ch) in truncated.chars().enumerate() {
349365
let col = text_start + j;
350366
if col < x2 - 1 {
351-
grid[row][col] = (ch, text_fg, *bold || rect.is_selected);
367+
grid[row][col] =
368+
(ch, text_fg, *bold || rect.is_selected);
352369
}
353370
}
354371
}
@@ -529,3 +546,101 @@ fn render_transform(frame: &mut Frame, app: &mut App, area: Rect) {
529546

530547
frame.render_stateful_widget(list, area, &mut app.transform_state);
531548
}
549+
550+
pub fn render_warning_modal(frame: &mut Frame, area: Rect, config_path: &str) {
551+
let path_w = config_path.len() as u16 + 14;
552+
let modal_w = path_w.max(48).min(area.width.saturating_sub(4));
553+
let modal_h = 15u16.min(area.height.saturating_sub(2));
554+
let x = (area.width.saturating_sub(modal_w)) / 2;
555+
let y = (area.height.saturating_sub(modal_h)) / 2;
556+
let modal_area = Rect::new(x, y, modal_w, modal_h);
557+
558+
frame.render_widget(Clear, modal_area);
559+
560+
let block = Block::default()
561+
.borders(Borders::ALL)
562+
.border_type(BorderType::Rounded)
563+
.border_style(Style::default().fg(Color::Red))
564+
.title(" Warning ");
565+
566+
let inner = block.inner(modal_area);
567+
frame.render_widget(block, modal_area);
568+
569+
let layout = Layout::default()
570+
.direction(Direction::Vertical)
571+
.constraints([Constraint::Min(1), Constraint::Length(3)])
572+
.split(inner);
573+
574+
let text = vec![
575+
Line::from(vec![Span::styled(
576+
" ⚠ Disable your last monitor?",
577+
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
578+
)]),
579+
Line::from(vec![Span::styled(
580+
" No way to undo from here.",
581+
Style::default().fg(Color::Yellow),
582+
)]),
583+
Line::from(""),
584+
Line::from(vec![Span::styled(
585+
" To recover, you'll need to:",
586+
Style::default().fg(Color::White),
587+
)]),
588+
Line::from(vec![Span::styled(
589+
" 1. Reboot your machine",
590+
Style::default().fg(Color::DarkGray),
591+
)]),
592+
Line::from(vec![Span::styled(
593+
" 2. Open a TTY session",
594+
Style::default().fg(Color::DarkGray),
595+
)]),
596+
Line::from(vec![
597+
Span::styled(" 3. Edit ", Style::default().fg(Color::DarkGray)),
598+
Span::styled(config_path, Style::default().fg(Color::Cyan)),
599+
]),
600+
Line::from(vec![Span::styled(
601+
" and remove the disable line",
602+
Style::default().fg(Color::DarkGray),
603+
)]),
604+
Line::from(vec![Span::styled(
605+
" 4. Reboot and log into your compositor",
606+
Style::default().fg(Color::DarkGray),
607+
)]),
608+
];
609+
610+
let buttons = vec![
611+
Line::from(vec![
612+
Span::styled(" ┌───────┐ ", Style::default().fg(Color::Red)),
613+
Span::styled("┌──────┐", Style::default().fg(Color::Green)),
614+
]),
615+
Line::from(vec![
616+
Span::styled(" │ ", Style::default().fg(Color::Red)),
617+
Span::styled(
618+
"[Y]",
619+
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
620+
),
621+
Span::styled("es ", Style::default().fg(Color::Red)),
622+
Span::styled("│ ", Style::default().fg(Color::Red)),
623+
Span::styled("│ ", Style::default().fg(Color::Green)),
624+
Span::styled(
625+
"[N]",
626+
Style::default()
627+
.fg(Color::Green)
628+
.add_modifier(Modifier::BOLD),
629+
),
630+
Span::styled("o ", Style::default().fg(Color::Green)),
631+
Span::styled("│", Style::default().fg(Color::Green)),
632+
]),
633+
Line::from(vec![
634+
Span::styled(" └───────┘ ", Style::default().fg(Color::Red)),
635+
Span::styled("└──────┘", Style::default().fg(Color::Green)),
636+
]),
637+
];
638+
639+
let text_widget =
640+
Paragraph::new(text).style(Style::default().fg(Color::White));
641+
frame.render_widget(text_widget, layout[0]);
642+
643+
let buttons_widget =
644+
Paragraph::new(buttons).style(Style::default().fg(Color::White));
645+
frame.render_widget(buttons_widget, layout[1]);
646+
}

0 commit comments

Comments
 (0)