forked from NotAShelf/beer
render: frame-paced presentation with per-row damage and blink
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I4e925b4d1d904d9592060e968d84ec906a6a6964
This commit is contained in:
parent
7887420139
commit
f1c8271d31
4 changed files with 274 additions and 102 deletions
27
src/grid.rs
27
src/grid.rs
|
|
@ -140,6 +140,8 @@ pub struct Grid {
|
|||
/// How many lines the viewport is scrolled back from the live bottom.
|
||||
view_offset: usize,
|
||||
cursor_shape: CursorShape,
|
||||
/// Whether the cursor shape is a blinking variant (DECSCUSR odd codes).
|
||||
cursor_blink: bool,
|
||||
cursor_visible: bool,
|
||||
/// Application cursor-keys mode (DECCKM): arrows send SS3 instead of CSI.
|
||||
app_cursor: bool,
|
||||
|
|
@ -177,6 +179,7 @@ impl Grid {
|
|||
scrollback: VecDeque::new(),
|
||||
view_offset: 0,
|
||||
cursor_shape: CursorShape::default(),
|
||||
cursor_blink: false,
|
||||
cursor_visible: true,
|
||||
cursor_color: None,
|
||||
app_cursor: false,
|
||||
|
|
@ -265,6 +268,14 @@ impl Grid {
|
|||
self.cursor_shape
|
||||
}
|
||||
|
||||
pub fn set_cursor_blink(&mut self, blink: bool) {
|
||||
self.cursor_blink = blink;
|
||||
}
|
||||
|
||||
pub fn cursor_blink(&self) -> bool {
|
||||
self.cursor_blink
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&mut self, visible: bool) {
|
||||
self.cursor_visible = visible;
|
||||
}
|
||||
|
|
@ -819,6 +830,22 @@ impl Grid {
|
|||
col >= lo && col <= hi
|
||||
}
|
||||
|
||||
/// The inclusive `(lo, hi)` column span selected on absolute row `row`, if
|
||||
/// any part of that row is selected.
|
||||
pub fn selection_span_on(&self, row: usize) -> Option<(usize, usize)> {
|
||||
let (start, end) = self.ordered_selection()?;
|
||||
if row < start.row || row > end.row {
|
||||
return None;
|
||||
}
|
||||
let lo = if row == start.row { start.col } else { 0 };
|
||||
let hi = if row == end.row {
|
||||
end.col
|
||||
} else {
|
||||
self.abs_row(row).len().saturating_sub(1)
|
||||
};
|
||||
Some((lo, hi))
|
||||
}
|
||||
|
||||
/// The selected text, with trailing blanks trimmed per line and rows joined
|
||||
/// by newlines. `None` if there is no selection.
|
||||
pub fn selection_text(&self) -> Option<String> {
|
||||
|
|
|
|||
106
src/render.rs
106
src/render.rs
|
|
@ -32,15 +32,6 @@ impl Canvas<'_> {
|
|||
Some((y as usize * self.width + x as usize) * 4)
|
||||
}
|
||||
|
||||
/// Fill the whole buffer with one colour (fast path, no per-pixel bounds
|
||||
/// checks).
|
||||
fn clear(&mut self, c: Rgb) {
|
||||
let bytes = [c.2, c.1, c.0, 0xff];
|
||||
for px in self.pixels.chunks_exact_mut(4) {
|
||||
px.copy_from_slice(&bytes);
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_rect(&mut self, x0: i32, y0: i32, w: u32, h: u32, c: Rgb) {
|
||||
let x_start = x0.max(0) as usize;
|
||||
let x_end = ((x0 + w as i32).max(0) as usize).min(self.width);
|
||||
|
|
@ -110,76 +101,79 @@ impl Renderer {
|
|||
self.fonts.metrics()
|
||||
}
|
||||
|
||||
/// Compose `grid` into `pixels` (BGRA, `width`×`height` px). `focused`
|
||||
/// selects a solid or hollow cursor.
|
||||
pub fn render(
|
||||
/// Repaint a single grid row `y` into `pixels` (BGRA, `width`×`height` px):
|
||||
/// clear the row band, fill backgrounds (and selection), draw glyphs and
|
||||
/// decorations, then the cursor if it sits on this row. `blink_on` is the
|
||||
/// current blink phase; blinking cells and a blinking cursor vanish when it
|
||||
/// is `false`. Painting one row at a time is what lets the caller damage
|
||||
/// only the rows that actually changed.
|
||||
pub fn render_row(
|
||||
&mut self,
|
||||
grid: &Grid,
|
||||
pixels: &mut [u8],
|
||||
width: usize,
|
||||
height: usize,
|
||||
dims: (usize, usize),
|
||||
grid: &Grid,
|
||||
y: usize,
|
||||
focused: bool,
|
||||
blink_on: bool,
|
||||
) {
|
||||
let (width, height) = dims;
|
||||
let mut canvas = Canvas {
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
canvas.clear(DEFAULT_BG);
|
||||
|
||||
let m = self.fonts.metrics();
|
||||
let cols = grid.cols();
|
||||
let row_top = y as i32 * m.height as i32;
|
||||
canvas.fill_rect(0, row_top, width as u32, m.height, DEFAULT_BG);
|
||||
|
||||
// Cell backgrounds: only paint cells that differ from the cleared
|
||||
// default - most of a screen is default background. Rows come through
|
||||
// the scrollback viewport and may be shorter than `cols` after a resize.
|
||||
for y in 0..grid.rows() {
|
||||
let abs = grid.view_to_abs(y);
|
||||
for (x, cell) in grid.view_row(y).iter().take(cols).enumerate() {
|
||||
let bg = if grid.is_selected(abs, x) {
|
||||
SELECTION_BG
|
||||
} else {
|
||||
cell_colors(cell).1
|
||||
};
|
||||
if bg != DEFAULT_BG {
|
||||
let (px, py) = (x as i32 * m.width as i32, y as i32 * m.height as i32);
|
||||
canvas.fill_rect(px, py, m.width, m.height, bg);
|
||||
}
|
||||
// Rows come through the scrollback viewport and may be shorter than
|
||||
// `cols` after a resize, so clamp with `take`.
|
||||
let abs = grid.view_to_abs(y);
|
||||
let cells = grid.view_row(y);
|
||||
for (x, cell) in cells.iter().take(cols).enumerate() {
|
||||
let bg = if grid.is_selected(abs, x) {
|
||||
SELECTION_BG
|
||||
} else {
|
||||
cell_colors(cell).1
|
||||
};
|
||||
if bg != DEFAULT_BG {
|
||||
canvas.fill_rect(x as i32 * m.width as i32, row_top, m.width, m.height, bg);
|
||||
}
|
||||
}
|
||||
|
||||
for y in 0..grid.rows() {
|
||||
for (x, cell) in grid.view_row(y).iter().take(cols).enumerate() {
|
||||
if cell.flags.contains(Flags::WIDE_CONT) {
|
||||
continue;
|
||||
}
|
||||
let (fg, _) = cell_colors(cell);
|
||||
let origin_x = x as i32 * m.width as i32;
|
||||
let cell_top = y as i32 * m.height as i32;
|
||||
if cell.c != ' ' {
|
||||
self.draw_glyph(
|
||||
&mut canvas,
|
||||
cell.c,
|
||||
cell_style(cell),
|
||||
origin_x,
|
||||
cell_top,
|
||||
fg,
|
||||
);
|
||||
}
|
||||
draw_decorations(&mut canvas, cell, origin_x, cell_top, m, fg);
|
||||
for (x, cell) in cells.iter().take(cols).enumerate() {
|
||||
if cell.flags.contains(Flags::WIDE_CONT) {
|
||||
continue;
|
||||
}
|
||||
if cell.flags.contains(Flags::BLINK) && !blink_on {
|
||||
continue;
|
||||
}
|
||||
let (fg, _) = cell_colors(cell);
|
||||
let origin_x = x as i32 * m.width as i32;
|
||||
if cell.c != ' ' {
|
||||
self.draw_glyph(&mut canvas, cell.c, cell_style(cell), origin_x, row_top, fg);
|
||||
}
|
||||
draw_decorations(&mut canvas, cell, origin_x, row_top, m, fg);
|
||||
}
|
||||
|
||||
// The cursor belongs to the live screen; hide it while scrolled back.
|
||||
if grid.view_at_bottom() {
|
||||
self.draw_cursor(&mut canvas, grid, m, focused);
|
||||
if grid.view_at_bottom() && grid.cursor().1 == y {
|
||||
self.draw_cursor(&mut canvas, grid, m, focused, blink_on);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the cursor: a solid block/underline/beam when focused, a hollow
|
||||
/// outline when not.
|
||||
fn draw_cursor(&mut self, canvas: &mut Canvas, grid: &Grid, m: CellMetrics, focused: bool) {
|
||||
if !grid.cursor_visible() {
|
||||
/// outline when not. A blinking cursor shape is only drawn while `blink_on`.
|
||||
fn draw_cursor(
|
||||
&mut self,
|
||||
canvas: &mut Canvas,
|
||||
grid: &Grid,
|
||||
m: CellMetrics,
|
||||
focused: bool,
|
||||
blink_on: bool,
|
||||
) {
|
||||
if !grid.cursor_visible() || (grid.cursor_blink() && !blink_on) {
|
||||
return;
|
||||
}
|
||||
let (cx, cy) = grid.cursor();
|
||||
|
|
|
|||
|
|
@ -473,11 +473,14 @@ impl Perform for Term {
|
|||
}),
|
||||
'q' if intermediates.first() == Some(&b'>') => self.report_version(),
|
||||
'q' if intermediates.first() == Some(&b' ') => {
|
||||
self.grid.set_cursor_shape(match raw(params, 0) {
|
||||
let code = raw(params, 0);
|
||||
self.grid.set_cursor_shape(match code {
|
||||
3 | 4 => CursorShape::Underline,
|
||||
5 | 6 => CursorShape::Beam,
|
||||
_ => CursorShape::Block,
|
||||
});
|
||||
// Even codes are steady; 0/1 and other odd codes blink.
|
||||
self.grid.set_cursor_blink(code == 0 || code % 2 == 1);
|
||||
}
|
||||
'p' if intermediates.contains(&b'$') => self.report_mode(params, private),
|
||||
'n' => self.device_status(params),
|
||||
|
|
|
|||
236
src/wayland.rs
236
src/wayland.rs
|
|
@ -9,12 +9,16 @@ use std::os::fd::OwnedFd;
|
|||
use std::os::unix::process::ExitStatusExt;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use calloop::generic::Generic;
|
||||
use calloop::timer::{TimeoutAction, Timer};
|
||||
use calloop::{EventLoop, Interest, LoopHandle, Mode, PostAction};
|
||||
use calloop_wayland_source::WaylandSource;
|
||||
|
||||
use crate::font::Fonts;
|
||||
use crate::grid::{Cell, CursorShape, Grid};
|
||||
use crate::pty::Pty;
|
||||
use crate::render::Renderer;
|
||||
use crate::vt::Term;
|
||||
|
|
@ -53,7 +57,10 @@ use smithay_client_toolkit::{
|
|||
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
|
||||
},
|
||||
},
|
||||
shm::{Shm, ShmHandler, slot::SlotPool},
|
||||
shm::{
|
||||
Shm, ShmHandler,
|
||||
slot::{Buffer, SlotPool},
|
||||
},
|
||||
};
|
||||
use wayland_client::{
|
||||
Connection, QueueHandle,
|
||||
|
|
@ -86,6 +93,55 @@ fn pick_mime(mimes: &[String]) -> Option<String> {
|
|||
/// Max gap between clicks counted as a multi-click (ms).
|
||||
const MULTI_CLICK_MS: u32 = 400;
|
||||
|
||||
/// Blink half-period: cells/cursor toggle visibility this often.
|
||||
const BLINK_MS: u64 = 500;
|
||||
|
||||
/// Buffers kept for double/triple buffering before we wait for a release.
|
||||
const MAX_BUFFERS: usize = 3;
|
||||
|
||||
/// What determines one rendered row's pixels: its cells, the cursor on it, the
|
||||
/// selection span over it, and the blink phase. Two equal `RowSnap`s render
|
||||
/// identically, so a buffer holding an equal snapshot needs no repaint.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
struct RowSnap {
|
||||
cells: Vec<Cell>,
|
||||
/// `(col, shape, focused)` when the cursor is drawn on this row.
|
||||
cursor: Option<(usize, CursorShape, bool)>,
|
||||
/// Inclusive selected column span on this row.
|
||||
sel: Option<(usize, usize)>,
|
||||
/// Blink phase, but only varied when the row actually has blinking ink, so
|
||||
/// non-blinking rows stay equal across phase toggles.
|
||||
blink: bool,
|
||||
}
|
||||
|
||||
/// One shm buffer plus the per-row snapshot of what it currently displays.
|
||||
#[derive(Debug)]
|
||||
struct FrameBuf {
|
||||
buffer: Buffer,
|
||||
rows: Vec<RowSnap>,
|
||||
}
|
||||
|
||||
/// Snapshot the determinants of viewport row `y`'s pixels.
|
||||
fn row_snap(grid: &Grid, y: usize, focused: bool, blink_on: bool) -> RowSnap {
|
||||
let abs = grid.view_to_abs(y);
|
||||
let cells = grid.view_row(y).to_vec();
|
||||
let cursor = if grid.view_at_bottom() && grid.cursor().1 == y {
|
||||
let visible = grid.cursor_visible() && (!grid.cursor_blink() || blink_on);
|
||||
visible.then(|| (grid.cursor().0, grid.cursor_shape(), focused))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let has_blink = cells
|
||||
.iter()
|
||||
.any(|c| c.flags.contains(crate::grid::Flags::BLINK));
|
||||
RowSnap {
|
||||
cells,
|
||||
cursor,
|
||||
sel: grid.selection_span_on(abs),
|
||||
blink: if has_blink { blink_on } else { true },
|
||||
}
|
||||
}
|
||||
|
||||
/// Default window size in pixels before the compositor suggests one.
|
||||
const DEFAULT_W: u32 = 800;
|
||||
const DEFAULT_H: u32 = 600;
|
||||
|
|
@ -159,22 +215,37 @@ pub fn run() -> anyhow::Result<ExitCode> {
|
|||
title: None,
|
||||
width: DEFAULT_W,
|
||||
height: DEFAULT_H,
|
||||
dirty: false,
|
||||
needs_draw: false,
|
||||
frame_pending: false,
|
||||
frames: Vec::new(),
|
||||
buf_dims: (0, 0),
|
||||
blink_on: true,
|
||||
focused: true,
|
||||
exit: false,
|
||||
exit_code: ExitCode::SUCCESS,
|
||||
};
|
||||
|
||||
// Block until an event (PTY output, input, configure) arrives - every
|
||||
// repaint is driven by one, so there is nothing to do on a timer.
|
||||
// Toggle the blink phase on a timer so blinking text and cursors animate.
|
||||
let blink = Timer::from_duration(Duration::from_millis(BLINK_MS));
|
||||
let blink_registered = event_loop
|
||||
.handle()
|
||||
.insert_source(blink, |_, _, app: &mut App| {
|
||||
app.blink_on = !app.blink_on;
|
||||
app.needs_draw = true;
|
||||
TimeoutAction::ToDuration(Duration::from_millis(BLINK_MS))
|
||||
});
|
||||
if let Err(err) = blink_registered {
|
||||
tracing::warn!("register blink timer: {err}");
|
||||
}
|
||||
|
||||
// Each iteration blocks until an event (PTY output, input, configure, frame
|
||||
// callback, blink) arrives, then presents at most one frame; bursts of PTY
|
||||
// output between frame callbacks coalesce into a single repaint.
|
||||
while !app.exit {
|
||||
event_loop
|
||||
.dispatch(None, &mut app)
|
||||
.context("dispatch event loop")?;
|
||||
if app.dirty {
|
||||
app.draw();
|
||||
app.dirty = false;
|
||||
}
|
||||
app.flush();
|
||||
}
|
||||
Ok(app.exit_code)
|
||||
}
|
||||
|
|
@ -244,8 +315,16 @@ struct App {
|
|||
title: Option<String>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
/// The grid changed and the window needs repainting.
|
||||
dirty: bool,
|
||||
/// The grid changed and the window wants repainting on the next frame.
|
||||
needs_draw: bool,
|
||||
/// A `wl_surface.frame` callback is in flight; defer drawing until it fires.
|
||||
frame_pending: bool,
|
||||
/// Double/triple-buffer ring, each tagged with the rows it currently shows.
|
||||
frames: Vec<FrameBuf>,
|
||||
/// Pixel size the `frames` buffers were allocated for.
|
||||
buf_dims: (u32, u32),
|
||||
/// Current blink phase, toggled by a timer; off hides blinking ink.
|
||||
blink_on: bool,
|
||||
/// Whether the toplevel currently has keyboard focus (drives the cursor).
|
||||
focused: bool,
|
||||
exit: bool,
|
||||
|
|
@ -341,7 +420,7 @@ impl App {
|
|||
-page
|
||||
};
|
||||
session.term.scroll_view(delta);
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -355,7 +434,7 @@ impl App {
|
|||
{
|
||||
session.term.scroll_to_bottom();
|
||||
session.term.grid_mut().clear_selection();
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
if let Err(err) = write_all(session.pty.master(), &bytes) {
|
||||
tracing::warn!("write key to pty: {err}");
|
||||
}
|
||||
|
|
@ -396,7 +475,7 @@ impl App {
|
|||
_ => grid.start_selection(row, col),
|
||||
}
|
||||
self.selecting = true;
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
}
|
||||
|
||||
/// Pointer motion during a drag: extend the selection head.
|
||||
|
|
@ -409,7 +488,7 @@ impl App {
|
|||
};
|
||||
if let Some(session) = self.session.as_mut() {
|
||||
session.term.grid_mut().extend_selection(row, col);
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -534,7 +613,7 @@ impl App {
|
|||
return;
|
||||
};
|
||||
session.term.scroll_to_bottom();
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
let bracketed = session.term.grid().bracketed_paste();
|
||||
// Strip control bytes a terminal must never receive raw from a paste;
|
||||
// keep tab and newlines (CR is what the shell expects for Enter).
|
||||
|
|
@ -575,7 +654,7 @@ impl App {
|
|||
self.window
|
||||
.set_title(self.title.clone().unwrap_or_default());
|
||||
}
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
}
|
||||
|
||||
/// Recompute the grid size for the current window and tell the grid and the
|
||||
|
|
@ -615,41 +694,106 @@ impl App {
|
|||
self.exit = true;
|
||||
}
|
||||
|
||||
/// Render the grid into a fresh buffer and present it.
|
||||
fn draw(&mut self) {
|
||||
/// Present a frame if one is wanted and the compositor is ready for it.
|
||||
/// Called after every event-loop wake; the frame-callback gate keeps draws
|
||||
/// paced to the display instead of one per PTY read.
|
||||
fn flush(&mut self) {
|
||||
if self.needs_draw && !self.frame_pending && self.session.is_some() {
|
||||
self.present();
|
||||
}
|
||||
}
|
||||
|
||||
/// Render only the rows that changed since the chosen buffer last displayed
|
||||
/// them, damage just those rows, and commit with a frame-callback request.
|
||||
fn present(&mut self) {
|
||||
self.needs_draw = false;
|
||||
let (w, h) = (self.width, self.height);
|
||||
let m = self.renderer.metrics();
|
||||
let (focused, blink_on) = (self.focused, self.blink_on);
|
||||
|
||||
// A resize invalidates every buffer's contents and size.
|
||||
if self.buf_dims != (w, h) {
|
||||
self.frames.clear();
|
||||
self.buf_dims = (w, h);
|
||||
}
|
||||
|
||||
let Some(session) = self.session.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let (w, h) = (self.width, self.height);
|
||||
let grid = session.term.grid();
|
||||
let rows = grid.rows();
|
||||
let cur: Vec<RowSnap> = (0..rows)
|
||||
.map(|y| row_snap(grid, y, focused, blink_on))
|
||||
.collect();
|
||||
|
||||
// Reuse a buffer the compositor has released, else grow the ring.
|
||||
let stride = w as i32 * 4;
|
||||
|
||||
let (buffer, canvas) =
|
||||
match self
|
||||
.pool
|
||||
.create_buffer(w as i32, h as i32, stride, wl_shm::Format::Argb8888)
|
||||
{
|
||||
Ok(buf) => buf,
|
||||
Err(err) => {
|
||||
tracing::error!("allocate shm buffer: {err}");
|
||||
return;
|
||||
let mut idx = None;
|
||||
for i in 0..self.frames.len() {
|
||||
if self.pool.canvas(&self.frames[i].buffer).is_some() {
|
||||
idx = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let idx = match idx {
|
||||
Some(i) => i,
|
||||
None if self.frames.len() < MAX_BUFFERS => {
|
||||
match self
|
||||
.pool
|
||||
.create_buffer(w as i32, h as i32, stride, wl_shm::Format::Argb8888)
|
||||
{
|
||||
Ok((buffer, _)) => {
|
||||
self.frames.push(FrameBuf {
|
||||
buffer,
|
||||
rows: Vec::new(),
|
||||
});
|
||||
self.frames.len() - 1
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("allocate shm buffer: {err}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// All buffers are still held by the compositor; a release event will
|
||||
// wake us and `needs_draw` (re-set below) retries then.
|
||||
None => {
|
||||
self.needs_draw = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.renderer.render(
|
||||
session.term.grid(),
|
||||
canvas,
|
||||
w as usize,
|
||||
h as usize,
|
||||
self.focused,
|
||||
);
|
||||
// Rows that differ from what this buffer last showed (all, if fresh).
|
||||
let prev = &self.frames[idx].rows;
|
||||
let dirty: Vec<usize> = (0..rows)
|
||||
.filter(|&y| prev.get(y) != Some(&cur[y]))
|
||||
.collect();
|
||||
if dirty.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(canvas) = self.pool.canvas(&self.frames[idx].buffer) else {
|
||||
return;
|
||||
};
|
||||
let dims = (w as usize, h as usize);
|
||||
for &y in &dirty {
|
||||
self.renderer
|
||||
.render_row(canvas, dims, grid, y, focused, blink_on);
|
||||
}
|
||||
self.frames[idx].rows = cur;
|
||||
|
||||
let surface = self.window.wl_surface();
|
||||
if let Err(err) = buffer.attach_to(surface) {
|
||||
if let Err(err) = self.frames[idx].buffer.attach_to(surface) {
|
||||
tracing::error!("attach buffer: {err}");
|
||||
return;
|
||||
}
|
||||
surface.damage_buffer(0, 0, w as i32, h as i32);
|
||||
for &y in &dirty {
|
||||
let top = y as i32 * m.height as i32;
|
||||
surface.damage_buffer(0, top, w as i32, m.height as i32);
|
||||
}
|
||||
surface.frame(&self.qh, surface.clone());
|
||||
self.window.commit();
|
||||
self.frame_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -672,7 +816,11 @@ impl CompositorHandler for App {
|
|||
) {
|
||||
}
|
||||
|
||||
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &wl_surface::WlSurface, _: u32) {}
|
||||
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &wl_surface::WlSurface, _: u32) {
|
||||
// The compositor is ready for another frame; `flush` will repaint if the
|
||||
// grid has changed since the last present.
|
||||
self.frame_pending = false;
|
||||
}
|
||||
|
||||
fn surface_enter(
|
||||
&mut self,
|
||||
|
|
@ -716,7 +864,7 @@ impl WindowHandler for App {
|
|||
} else {
|
||||
self.resize_grid();
|
||||
}
|
||||
self.draw();
|
||||
self.needs_draw = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -811,7 +959,7 @@ impl KeyboardHandler for App {
|
|||
) {
|
||||
self.serial = serial;
|
||||
self.focused = true;
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
}
|
||||
|
||||
fn leave(
|
||||
|
|
@ -823,7 +971,7 @@ impl KeyboardHandler for App {
|
|||
_: u32,
|
||||
) {
|
||||
self.focused = false;
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
}
|
||||
|
||||
fn press_key(
|
||||
|
|
@ -936,7 +1084,7 @@ impl PointerHandler for App {
|
|||
let delta = if raw < 0.0 { lines } else { -lines };
|
||||
if let Some(session) = self.session.as_mut() {
|
||||
session.term.scroll_view(delta);
|
||||
self.dirty = true;
|
||||
self.needs_draw = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue