Phase 4: Steuerelemente implementieren

This commit is contained in:
2026-09-05 12:49:46 +02:00
parent 1cb6ae8fd9
commit 19804e0e2d
36 changed files with 6472 additions and 689 deletions

View File

@@ -24,6 +24,9 @@ use unicode_width::UnicodeWidthChar;
pub const MIN_COLS: usize = 80;
pub const MIN_ROWS: usize = 25;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScreenError;
/// Eine Bildschirmzelle: Zeichen plus klassisches Farbattribut.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cell {
@@ -78,6 +81,9 @@ pub struct TextScreen {
/// Seit der letzten Anzeige verändert? Der Host wird nur dann zum
/// Neuzeichnen aufgefordert.
veraendert: bool,
graphic_x: i32,
graphic_y: i32,
graphic_view: Option<(i32, i32, i32, i32)>,
}
impl Default for TextScreen {
@@ -110,6 +116,9 @@ impl TextScreen {
view_full: true,
belegt: vec![None; rows],
veraendert: true,
graphic_x: 0,
graphic_y: 0,
graphic_view: None,
}
}
@@ -146,8 +155,8 @@ impl TextScreen {
}
self.cells = cells;
let mut belegt = vec![None; rows];
for r in 0..self.rows.min(rows) {
belegt[r] = self.belegt[r].map(|c| c.min(cols - 1));
for (r, value) in belegt.iter_mut().enumerate().take(self.rows.min(rows)) {
*value = self.belegt[r].map(|c| c.min(cols - 1));
}
self.belegt = belegt;
self.cols = cols;
@@ -225,9 +234,9 @@ impl TextScreen {
}
/// `LOCATE zeile, spalte` (1-basiert); außerhalb → Err (Fehler 5).
pub fn locate(&mut self, row: usize, col: usize) -> Result<(), ()> {
pub fn locate(&mut self, row: usize, col: usize) -> Result<(), ScreenError> {
if row < 1 || row > self.rows || col < 1 || col > self.cols {
return Err(());
return Err(ScreenError);
}
self.cur_row = row - 1;
// Auf eine Fortsetzungszelle zu zeigen bedeutet den Zeichenanfang.
@@ -251,9 +260,9 @@ impl TextScreen {
}
/// `VIEW PRINT oben TO unten` (1-basiert).
pub fn view_print(&mut self, top: usize, bottom: usize) -> Result<(), ()> {
pub fn view_print(&mut self, top: usize, bottom: usize) -> Result<(), ScreenError> {
if top < 1 || bottom > self.rows || top > bottom {
return Err(());
return Err(ScreenError);
}
self.view_top = top - 1;
self.view_bottom = bottom - 1;
@@ -265,6 +274,122 @@ impl TextScreen {
self.cells[(row - 1) * self.cols + (col - 1)]
}
pub fn graphics_position(&self) -> (i32, i32) {
(self.graphic_x, self.graphic_y)
}
pub fn graphics_view(&mut self, view: Option<(i32, i32, i32, i32)>) -> Result<(), ScreenError> {
if view.is_some_and(|(x1, y1, x2, y2)| x1 < 0 || y1 < 0 || x1 > x2 || y1 > y2) {
return Err(ScreenError);
}
self.graphic_view = view;
Ok(())
}
fn graphics_cell(&mut self, x: i32, y: i32, color: i32) {
if x < 0
|| y < 0
|| self
.graphic_view
.is_some_and(|(x1, y1, x2, y2)| x < x1 || x > x2 || y < y1 || y > y2)
{
return;
}
let (col, row) = (x as usize / 8, y as usize / 8);
if col >= self.cols || row >= self.rows {
return;
}
self.cells[row * self.cols + col] = Cell {
ch: if color == 0 { ' ' } else { '█' },
fg: color.clamp(0, 15) as u8,
bg: self.bg,
fortsetzung: false,
};
self.belegt[row] = Some(self.belegt[row].map_or(col, |last| last.max(col)));
self.veraendert = true;
}
pub fn graphics_line(&mut self, from: (i32, i32), to: (i32, i32), color: i32, fill: u8) {
let (x1, y1) = from;
let (x2, y2) = to;
if fill == 2 {
for y in (y1.min(y2)..=y1.max(y2)).step_by(8) {
for x in (x1.min(x2)..=x1.max(x2)).step_by(8) {
self.graphics_cell(x, y, color);
}
}
} else if fill == 1 {
self.graphics_segment(x1, y1, x2, y1, color);
self.graphics_segment(x2, y1, x2, y2, color);
self.graphics_segment(x2, y2, x1, y2, color);
self.graphics_segment(x1, y2, x1, y1, color);
} else {
self.graphics_segment(x1, y1, x2, y2, color);
}
self.graphic_x = x2;
self.graphic_y = y2;
}
fn graphics_segment(&mut self, mut x: i32, mut y: i32, x2: i32, y2: i32, color: i32) {
let dx = (x2 - x).abs();
let sx = if x < x2 { 1 } else { -1 };
let dy = -(y2 - y).abs();
let sy = if y < y2 { 1 } else { -1 };
let mut error = dx + dy;
loop {
self.graphics_cell(x, y, color);
if x == x2 && y == y2 {
break;
}
let twice = error * 2;
if twice >= dy {
error += dy;
x += sx;
}
if twice <= dx {
error += dx;
y += sy;
}
}
}
pub fn graphics_paint(&mut self, x: i32, y: i32, color: i32) {
let (start_col, start_row) = (x.max(0) as usize / 8, y.max(0) as usize / 8);
if start_col >= self.cols || start_row >= self.rows {
return;
}
let target = self.cells[start_row * self.cols + start_col].ch;
let replacement = if color == 0 { ' ' } else { '█' };
if target == replacement {
return;
}
let mut pending = vec![(start_col, start_row)];
while let Some((col, row)) = pending.pop() {
let px = col as i32 * 8;
let py = row as i32 * 8;
if col >= self.cols
|| row >= self.rows
|| self.cells[row * self.cols + col].ch != target
|| self
.graphic_view
.is_some_and(|(x1, y1, x2, y2)| px < x1 || px > x2 || py < y1 || py > y2)
{
continue;
}
self.graphics_cell(px, py, color);
if col > 0 {
pending.push((col - 1, row));
}
if row > 0 {
pending.push((col, row - 1));
}
pending.push((col + 1, row));
pending.push((col, row + 1));
}
self.graphic_x = x;
self.graphic_y = y;
}
/// Text an der Cursorposition ausgeben: Umbruch am rechten Rand,
/// Scrollen am unteren Rand des Scrollbereichs. `\n` bricht um,
/// `\r` setzt an den Zeilenanfang.