Phase 6: VBDOS-Theme umsetzen und Change 05a archivieren

This commit is contained in:
2026-09-07 20:25:55 +02:00
parent 69f2002481
commit 5aa920b768
22 changed files with 686 additions and 79 deletions

View File

@@ -7,12 +7,7 @@ use crate::{
};
use anyhow::{anyhow, ensure, Result};
use crossterm::event::{KeyCode as K, KeyEvent, KeyModifiers as M};
use ratatui::{
layout::Rect,
style::{Color, Style},
widgets::Paragraph,
Frame,
};
use ratatui::{layout::Rect, widgets::Paragraph, Frame};
use tb_frontend::{Diagnostic, SourcePos};
use tb_vm::interp::{DebugLocation, DebugWatch, Vm};
@@ -682,8 +677,7 @@ impl App {
}
}
f.render_widget(
Paragraph::new(lines.join("\n"))
.style(Style::default().fg(Color::White).bg(Color::Blue)),
Paragraph::new(lines.join("\n")).style(crate::render::pair(15, 1)),
r,
);
if kind == WindowKind::Immediate && r.height > 4 {

View File

@@ -1702,7 +1702,7 @@ impl App {
let r = Rect::new(area.x + timers * 4, area.bottom().saturating_sub(1), 3, 1);
timers += 1;
f.render_widget(
Paragraph::new("[T]").style(Style::default().fg(crate::render::dos(14))),
Paragraph::new("[T]").style(crate::render::pair(0, 14)),
r.intersection(area),
);
r
@@ -1749,7 +1749,7 @@ impl App {
(r.right() - 1, r.y + r.height / 2),
] {
f.render_widget(
Paragraph::new("").style(Style::default().fg(crate::render::dos(14))),
Paragraph::new("").style(crate::render::pair(0, 14)),
Rect::new(x, y, 1, 1),
);
}
@@ -1789,9 +1789,15 @@ impl App {
if !r.is_empty() {
f.render_widget(
Paragraph::new(format!("{n:X} ")).style(
Style::default()
.bg(crate::render::dos(n as u8))
.fg(crate::render::dos(if n < 8 { 15 } else { 0 })),
Style::default().bg(crate::render::dos(n as u8)).fg(
crate::render::dos(
if matches!(n, 0 | 1 | 4 | 5 | 6 | 8 | 9) {
15
} else {
0
},
),
),
),
r,
);

View File

@@ -8,7 +8,7 @@ use crossterm::event::{KeyCode as K, KeyEvent, KeyModifiers as M};
use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd};
use ratatui::{
layout::Rect,
style::{Color, Modifier, Style},
style::{Modifier, Style},
text::{Line, Span},
widgets::Paragraph,
Frame,
@@ -165,7 +165,7 @@ impl Page {
style = style.add_modifier(Modifier::ITALIC);
}
if heading.is_some() {
style = style.fg(Color::Yellow);
style = style.fg(crate::render::dos(1));
}
match event {
Event::Start(Tag::Heading { level, .. }) => {
@@ -864,7 +864,7 @@ impl App {
let start = self.help.row(&rows);
f.render_widget(
Paragraph::new(format!("{} · Suche: {}", page.title, self.help.input))
.style(Style::default().fg(Color::Yellow)),
.style(Style::default().fg(crate::render::dos(1))),
Rect::new(area.x, area.y, area.width, 1),
);
for (y, row) in rows
@@ -887,9 +887,11 @@ impl App {
if x >= shift && end <= shift + area.width as usize {
let mut style = g.style;
if let Some(link) = g.link {
style = style.fg(Color::Cyan).add_modifier(Modifier::UNDERLINED);
style = style
.fg(crate::render::dos(1))
.add_modifier(Modifier::UNDERLINED);
if self.help.position.link == Some(link) {
style = style.bg(Color::Blue).fg(Color::White);
style = style.bg(crate::render::dos(1)).fg(crate::render::dos(15));
}
}
if let Some(last) = spans.last_mut().filter(|s| s.style == style) {

View File

@@ -11,29 +11,82 @@ use ratatui::{
};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
/// DOS-Farben der IDE. Die ersten 16 ANSI-Einträge gehören dem Terminalprofil.
/// BASIC-Ausgabe verwendet weiterhin ihre eigene Farbabbildung in tb-ui.
pub fn dos(n: u8) -> Color {
[
Color::Black,
Color::Blue,
Color::Green,
Color::Cyan,
Color::Red,
Color::Magenta,
Color::Yellow,
Color::Gray,
Color::DarkGray,
Color::LightBlue,
Color::LightGreen,
Color::LightCyan,
Color::LightRed,
Color::LightMagenta,
Color::LightYellow,
Color::White,
][n.min(15) as usize]
static COLORS: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
dos_color(
n,
*COLORS.get_or_init(|| {
let detected = crossterm::style::available_color_count();
// Ein generisches COLORTERM (z.B. "yes") verdeckt bei crossterm TERM.
if detected < 256 && std::env::var("TERM").is_ok_and(|s| s.contains("256")) {
256
} else {
detected
}
}),
)
}
pub fn dos_color(n: u8, colors: u16) -> Color {
let n = n.min(15) as usize;
const RGB: [(u8, u8, u8); 16] = [
(0, 0, 0),
(0, 0, 170),
(0, 170, 0),
(0, 170, 170),
(170, 0, 0),
(170, 0, 170),
(170, 85, 0),
(170, 170, 170),
(85, 85, 85),
(85, 85, 255),
(85, 255, 85),
(85, 255, 255),
(255, 85, 85),
(255, 85, 255),
(255, 255, 85),
(255, 255, 255),
];
if colors > 256 {
let (r, g, b) = RGB[n];
Color::Rgb(r, g, b)
} else if colors >= 256 {
// Fester 6x6x6-Farbwürfel / Graurampe statt profilabhängiger Systemfarben.
Color::Indexed(
[
16, 19, 34, 37, 124, 127, 130, 248, 240, 63, 83, 87, 203, 207, 227, 231,
][n],
)
} else {
[
Color::Black,
Color::Blue,
Color::Green,
Color::Cyan,
Color::Red,
Color::Magenta,
Color::Yellow,
Color::Gray,
Color::DarkGray,
Color::LightBlue,
Color::LightGreen,
Color::LightCyan,
Color::LightRed,
Color::LightMagenta,
Color::LightYellow,
Color::White,
][n]
}
}
pub(crate) fn pair(fg: u8, bg: u8) -> Style {
Style::default().fg(dos(fg)).bg(dos(bg))
}
fn style(app: &App, element: usize) -> Style {
let (fg, bg) = app.options.colors[element];
Style::default().fg(dos(fg)).bg(dos(bg))
pair(fg, bg)
}
fn put(f: &mut Frame, area: Rect, text: impl Into<String>, style: Style) {
f.render_widget(Paragraph::new(text.into()).style(style), area);
@@ -72,7 +125,7 @@ impl App {
f,
area,
"Terminal Basic benötigt mindestens 80×25 Zellen.",
Style::default(),
pair(7, 0),
);
return;
}
@@ -101,6 +154,8 @@ impl App {
self,
if matches!(w.kind, WindowKind::Code(_)) {
2
} else if w.kind == WindowKind::Toolbox {
6
} else {
5
},
@@ -108,6 +163,11 @@ impl App {
f.render_widget(
Block::default()
.borders(Borders::ALL)
.border_type(if active {
ratatui::widgets::BorderType::Double
} else {
ratatui::widgets::BorderType::Plain
})
.style(content_style)
.border_style(style(self, 4)),
rect,
@@ -240,7 +300,7 @@ impl App {
1,
),
if mark.bound { "" } else { "" },
Style::default().fg(Color::Red),
pair(15, 4),
);
}
}
@@ -290,7 +350,7 @@ impl App {
1,
),
mark,
Style::default().fg(Color::Yellow),
pair(0, 14),
);
}
}
@@ -358,12 +418,12 @@ impl App {
Paragraph::new(text)
.block(
Block::bordered()
.border_type(ratatui::widgets::BorderType::Double)
.border_style(if active {
Style::default().fg(Color::White)
.border_type(if active {
ratatui::widgets::BorderType::Double
} else {
style(self, 4)
}),
ratatui::widgets::BorderType::Plain
})
.border_style(style(self, 4)),
)
.style(content_style),
r,
@@ -396,7 +456,7 @@ impl App {
r,
text,
if row == self.selected_member {
Style::default().fg(Color::White).bg(Color::Black)
pair(15, 0)
} else {
content_style
},
@@ -474,13 +534,13 @@ impl App {
f,
Rect::new(0, 0, area.width - width, 1),
left,
style(self, 0),
style(self, 6),
);
put(
f,
Rect::new(area.width - width, 0, width, 1),
geometry,
style(self, 0),
style(self, 6),
);
self.hits.push((
Rect::new(0, 0, area.width / 2, 1),
@@ -508,14 +568,14 @@ impl App {
Span::raw(" "),
Span::styled(
&m.title[..1],
style(self, 0).add_modifier(Modifier::UNDERLINED),
Style::default().add_modifier(Modifier::UNDERLINED),
),
Span::raw(format!("{} ", &m.title[1..])),
];
f.render_widget(
Paragraph::new(Line::from(spans)).style(
if self.menu.is_some_and(|(n, _)| n == i) {
Style::default().fg(Color::White).bg(Color::Black)
pair(15, 0)
} else {
style(self, 0)
},
@@ -551,14 +611,16 @@ impl App {
for (i, (text, c)) in entries.iter().enumerate().skip(start).take(visible) {
let disabled = c.and_then(|c| self.availability(c));
let st = if disabled.is_some() {
Style::default().fg(Color::DarkGray).bg(Color::Gray)
pair(0, 7).add_modifier(Modifier::ITALIC)
} else if i == selected {
Style::default().fg(Color::White).bg(Color::Black)
pair(15, 0)
} else {
style(self, 0)
};
let r = Rect::new(x + 1, 2 + (i - start) as u16, width - 2, 1);
let label = if *c == Some(Command::SyntaxChecking) && self.options.syntax_checking {
let label = if disabled.is_some() {
format!("× {text}")
} else if *c == Some(Command::SyntaxChecking) && self.options.syntax_checking {
format!("{text}")
} else {
text.clone()
@@ -605,7 +667,13 @@ impl App {
);
let st = style(self, 5);
f.render_widget(Clear, rect);
f.render_widget(Block::bordered().title(d.title.clone()).style(st), rect);
f.render_widget(Block::bordered().style(st), rect);
put(
f,
Rect::new(rect.x, rect.y, rect.width, 1),
d.title.clone(),
style(self, 3),
);
let visible = height.saturating_sub(7) as usize;
let start = d
.focus
@@ -626,11 +694,7 @@ impl App {
};
let label = format!("{}: {}", field.label, value);
let selected = d.focus == i;
let field_style = if selected {
Style::default().fg(Color::White).bg(Color::Black)
} else {
st
};
let field_style = if selected { pair(15, 0) } else { st };
let caret = if let FieldValue::Text(text) = &field.value {
field.label.width() + 2 + text[..field.cursor].width()
} else {
@@ -671,7 +735,7 @@ impl App {
"[OK / Enter]"
},
if d.focus == d.fields.len() {
Style::default().fg(Color::White).bg(Color::Black)
pair(15, 0)
} else {
st
},
@@ -684,7 +748,7 @@ impl App {
f,
Rect::new(rect.x + 36, rect.bottom() - 3, width - 38, 1),
"[Erzeugen: Phase 6]",
Style::default().fg(Color::DarkGray).bg(Color::Gray),
pair(0, 7).add_modifier(Modifier::ITALIC),
);
}
let mut status = d.error.clone();