blob: 05146e83bc716eca6eb35ce53b11a70f32bbe4b7 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![allow(dead_code)]
use {
crate::ui::terminal_views::{GridView, ScrollBar},
carnelian::{
color::Color,
input::{self},
render::{Composition, Context as RenderContext, PreClear, RenderExt},
Coord, Rect, Size, ViewAssistantContext,
},
fuchsia_trace as ftrace,
term_model::term::RenderableCellsIter,
};
pub struct TerminalScene {
composition: Composition,
background_color: Color,
grid_view: GridView,
scroll_bar: ScrollBar,
size: Size,
scroll_context: ScrollContext,
}
const SCROLL_BAR_WIDTH: f32 = 16.0;
pub enum PointerEventResponse {
/// Indicates that the pointer event resulted in the user wanting to scroll the grid
ScrollLines(isize),
/// Indicates that there are no changes in the grid but the view needs to be updated.
/// This is usually in response to the scroll bar scrolling but not changing the lines.
ViewDirty,
}
impl TerminalScene {
pub fn new(background_color: Color) -> TerminalScene {
let composition = Composition::new(background_color);
TerminalScene { background_color, composition, ..TerminalScene::default() }
}
pub fn update_background_color(&mut self, new_color: Color) {
self.background_color = new_color;
}
pub fn calculate_term_size_from_size(size: &Size) -> Size {
Size::new(size.width - SCROLL_BAR_WIDTH, size.height)
}
pub fn render<'a, C>(
&mut self,
render_context: &mut RenderContext,
context: &ViewAssistantContext,
cells: RenderableCellsIter<'a, C>,
) {
ftrace::duration!("terminal", "Scene:TerminalScene:render2");
let image = render_context.get_current_image(context);
let ext = RenderExt {
pre_clear: Some(PreClear { color: self.background_color }),
..Default::default()
};
let grid_layers = self.grid_view.render(render_context, cells);
let scroll_bar_layers = self.scroll_bar.render(render_context);
self.composition.replace(.., grid_layers.into_iter().chain(scroll_bar_layers));
render_context.render(&self.composition, None, image, &ext);
}
pub fn update_size(&mut self, new_size: Size, cell_size: Size) {
ftrace::duration!("terminal", "Scene:TerminalScene:update_size");
self.grid_view.cell_size = cell_size;
self.size = new_size;
self.grid_view.frame =
Rect::from_size(TerminalScene::calculate_term_size_from_size(&new_size));
let mut scroll_bar_frame = Rect::from_size(new_size);
scroll_bar_frame.size.width = SCROLL_BAR_WIDTH;
scroll_bar_frame.origin.x = new_size.width - SCROLL_BAR_WIDTH;
self.scroll_bar.frame = scroll_bar_frame;
self.update_scroll_metrics();
}
pub fn update_scroll_context(&mut self, scroll_context: ScrollContext) {
if scroll_context != self.scroll_context {
self.scroll_context = scroll_context;
self.update_scroll_metrics();
}
}
pub fn handle_pointer_event(
&mut self,
event: &input::pointer::Event,
_ctx: &mut ViewAssistantContext,
) -> Option<PointerEventResponse> {
if self.scroll_bar.is_tracking() {
return self.handle_primary_pointer_event_for_scroll_bar(&event);
} else {
match event.phase {
input::pointer::Phase::Down(point) => {
let point = point.to_f32();
if self.scroll_bar.frame.contains(point) {
return self.handle_primary_pointer_event_for_scroll_bar(&event);
}
}
_ => (),
}
}
None
}
fn update_scroll_metrics(&mut self) {
let total_lines = self.scroll_context.history + self.scroll_context.visible_lines;
let content_height = self.grid_view.cell_size.height * (total_lines as Coord);
let content_offset =
self.grid_view.cell_size.height * (self.scroll_context.display_offset as Coord);
self.scroll_bar.content_height = content_height;
self.scroll_bar.content_offset = content_offset;
self.scroll_bar.invalidate_thumb_frame();
}
fn handle_primary_pointer_event_for_scroll_bar(
&mut self,
event: &input::pointer::Event,
) -> Option<PointerEventResponse> {
// The following logic assumes that we are only tracking one pointer at a time.
let prev_offset = self.scroll_bar.content_offset;
match event.phase {
input::pointer::Phase::Down(location) => {
self.scroll_bar.begin_tracking_pointer_event(location.to_f32());
}
input::pointer::Phase::Moved(location) => {
self.scroll_bar.handle_pointer_move(location.to_f32())
}
input::pointer::Phase::Up
| input::pointer::Phase::Remove
| input::pointer::Phase::Cancel => self.scroll_bar.cancel_pointer_event(),
};
// do not rely on the ScrollContext being udpated at this point. Rely on the
// values stored in the scroll bar to determine how many rows we have changed.
let offset_change = Self::calculate_change_in_scroll_offset(
prev_offset,
self.scroll_bar.content_offset,
self.grid_view.cell_size.height,
);
match offset_change {
0 => Some(PointerEventResponse::ViewDirty),
_ => Some(PointerEventResponse::ScrollLines(offset_change)),
}
}
fn calculate_change_in_scroll_offset(
previous_content_offset: f32,
content_offset: f32,
row_height: f32,
) -> isize {
let new_row = f32::floor(content_offset / row_height);
let old_row = f32::floor(previous_content_offset / row_height);
(new_row - old_row) as isize
}
}
impl Default for TerminalScene {
fn default() -> Self {
let background_color = Color::new();
let composition = Composition::new(background_color);
TerminalScene {
composition,
background_color,
size: Size::zero(),
grid_view: GridView::new(&background_color),
scroll_bar: ScrollBar::default(),
scroll_context: ScrollContext::default(),
}
}
}
#[derive(Eq, PartialEq)]
pub struct ScrollContext {
pub history: usize,
pub visible_lines: usize,
pub display_offset: usize,
}
impl Default for ScrollContext {
fn default() -> Self {
ScrollContext { history: 0, visible_lines: 0, display_offset: 0 }
}
}
#[cfg(test)]
mod tests {
use {super::*, carnelian::Point};
#[test]
fn calculate_difference_in_display_offset_no_change() {
let row_height = 10.0;
let content_offset = 10.1;
let prev_offset = 10.0;
// jump from row 1 to row 1
let delta = TerminalScene::calculate_change_in_scroll_offset(
prev_offset,
content_offset,
row_height,
);
assert_eq!(delta, 0);
}
#[test]
fn calculate_difference_in_display_offset_single_line() {
let row_height = 10.0;
let content_offset = 20.1;
let prev_offset = 10.0;
// jump from row 1 to row 2
let delta = TerminalScene::calculate_change_in_scroll_offset(
prev_offset,
content_offset,
row_height,
);
assert_eq!(delta, 1);
}
#[test]
fn calculate_difference_in_display_offset_down_single_line() {
let row_height = 10.0;
let content_offset = 19.0;
let prev_offset = 20.0;
// jump from row 2 to row 1
let delta = TerminalScene::calculate_change_in_scroll_offset(
prev_offset,
content_offset,
row_height,
);
assert_eq!(delta, -1);
}
#[test]
fn calculate_difference_in_display_offset_multiple_lines() {
let row_height = 10.0;
let content_offset = 25.0;
let prev_offset = 0.0;
// jump from row 0 to row 2
let delta = TerminalScene::calculate_change_in_scroll_offset(
prev_offset,
content_offset,
row_height,
);
assert_eq!(delta, 2);
}
#[test]
fn new_with_color_sets_background_color() {
let scene = TerminalScene::new(Color { r: 255, ..Color::new() });
assert_eq!(scene.background_color.r, 255);
}
#[test]
fn update_background_color_sets_color_of_bg_view() {
let mut scene = TerminalScene::default();
scene.update_background_color(Color { g: 255, ..Color::new() });
assert_eq!(scene.background_color.g, 255);
}
#[test]
fn update_cell_size_updates_grid_view() {
let mut scene = TerminalScene::default();
scene.update_size(Size::zero(), Size::new(99.0, 99.0));
assert_eq!(scene.grid_view.cell_size, Size::new(99.0, 99.0));
}
#[test]
fn calculate_term_size_has_affordance_for_scroll_bar() {
let size = Size::new(100.0, 100.0);
let calculated = TerminalScene::calculate_term_size_from_size(&size);
assert_eq!(calculated.width, 84.0);
assert_eq!(calculated.height, 100.0);
}
#[test]
fn update_size_sets_frames() {
let mut scene = TerminalScene::default();
let size = Size::new(100.0, 100.0);
scene.update_size(size, Size::zero());
assert_eq!(scene.grid_view.frame, Rect::new(Point::zero(), Size::new(84.0, 100.0)));
assert_eq!(
scene.scroll_bar.frame,
Rect::new(Point::new(84.0, 0.0), Size::new(16.0, 100.0))
);
}
#[test]
fn update_scroll_context_updates_content_height() {
let mut scene = TerminalScene::default();
scene.update_size(Size::zero(), Size::new(10.0, 10.0));
assert_eq!(scene.scroll_bar.content_height, 0.0);
let scroll_context = ScrollContext { history: 300, visible_lines: 2, display_offset: 0 };
scene.update_scroll_context(scroll_context);
assert_eq!(scene.scroll_bar.content_height, 3020.0);
}
#[test]
fn update_scroll_context_updates_content_offset() {
let mut scene = TerminalScene::default();
scene.update_size(Size::zero(), Size::new(10.0, 10.0));
let scroll_context = ScrollContext { history: 300, visible_lines: 2, display_offset: 10 };
scene.update_scroll_context(scroll_context);
assert_eq!(scene.scroll_bar.content_offset, 100.0);
}
#[test]
fn update_cell_size_updates_content_size() {
let mut scene = TerminalScene::default();
let scroll_context = ScrollContext { history: 300, visible_lines: 2, display_offset: 0 };
scene.update_scroll_context(scroll_context);
scene.update_size(Size::zero(), Size::new(10.0, 5.0));
assert_eq!(scene.scroll_bar.content_height, 1510.0);
}
#[test]
fn update_cell_size_updates_content_offset() {
let mut scene = TerminalScene::default();
// originally frame is much bigger than content so there is no offset
scene.update_size(Size::zero(), Size::new(10.0, 10.0));
let scroll_context = ScrollContext { history: 10, visible_lines: 10, display_offset: 10 };
scene.update_scroll_context(scroll_context);
assert_eq!(scene.scroll_bar.content_offset, 100.0);
scene.update_size(Size::new(100.0, 1000.0), Size::new(10.0, 5.0));
assert_eq!(scene.scroll_bar.content_offset, 50.0);
}
}