blob: bdb60df89a5649c6fa03e0e24ed3c64cb54805b0 [file] [log] [blame]
// Copyright 2021 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.
use {
anyhow::Error,
carnelian::{
color::Color,
drawing::path_for_rounded_rectangle,
render::{BlendMode, Context as RenderContext, Fill, FillRule, Layer, Raster, Style},
scene::{facets::Facet, LayerGroup, SceneOrder},
AppSender, Coord, Rect, Size, ViewAssistantContext, ViewKey,
},
fuchsia_trace as ftrace,
std::time::{Duration, Instant},
std::{any::Any, cell::RefCell, rc::Rc},
term_model::{ansi::TermInfo, config::Config, Term},
terminal::{renderable_layers, FontSet, Offset, Renderer},
};
/// Empty type for term model config
pub struct UIConfig;
impl Default for UIConfig {
fn default() -> UIConfig {
UIConfig
}
}
pub type TerminalConfig = Config<UIConfig>;
pub enum TerminalMessages {
SetScrollThumbMessage(Option<(Rect, f32)>),
/// Scroll thumb fade-out starting time and duration.
SetScrollThumbFadeOutMessage(Option<(Instant, Duration)>),
}
fn raster_for_rounded_rectangle(
bounds: &Rect,
corner_radius: Coord,
render_context: &mut RenderContext,
) -> Raster {
let mut raster_builder = render_context.raster_builder().expect("raster_builder");
raster_builder.add(&path_for_rounded_rectangle(bounds, corner_radius, render_context), None);
raster_builder.build()
}
// Computes the current scroll thumb alpha value for a fade-out effect started for `time_elapsed` with a duration of `total_duration`.
fn scroll_thumb_fade_out_alpha(total_duration: f32, time_elapsed: f32, alpha: f32) -> f32 {
alpha - (alpha / total_duration) * time_elapsed
}
/// Facet that implements a terminal text grid with a scroll bar.
pub struct TerminalFacet<T> {
app_sender: Option<AppSender>,
view_key: ViewKey,
font_set: FontSet,
size: Size,
term: Rc<RefCell<Term<T>>>,
scroll_thumb: Option<(Rect, f32)>,
scroll_thumb_fade_out: Option<(Instant, Duration)>,
thumb_order: Option<SceneOrder>,
renderer: Renderer,
}
impl<T: 'static> TerminalFacet<T> {
pub fn new(
app_sender: Option<AppSender>,
view_key: ViewKey,
font_set: FontSet,
cell_size: &Size,
term: Rc<RefCell<Term<T>>>,
) -> Self {
let renderer = Renderer::new(&font_set, cell_size);
TerminalFacet {
app_sender,
view_key,
font_set,
size: Size::zero(),
term,
scroll_thumb: None,
scroll_thumb_fade_out: None,
thumb_order: None,
renderer,
}
}
}
impl<T: 'static> Facet for TerminalFacet<T> {
fn update_layers(
&mut self,
_: Size,
layer_group: &mut dyn LayerGroup,
render_context: &mut RenderContext,
view_context: &ViewAssistantContext,
) -> Result<(), Error> {
ftrace::duration!(c"terminal", c"TerminalFacet:update_layers");
self.size = view_context.size;
let config = TerminalConfig::default();
let term = self.term.borrow();
let cols = term.cols().0;
let rows = term.lines().0;
let stride = cols * 4;
let new_thumb_order =
SceneOrder::try_from(stride * rows).unwrap_or_else(|e| panic!("{}", e));
// Remove old scrollbar thumb.
if let Some(thumb_order) = self.thumb_order.take() {
layer_group.remove(thumb_order);
}
let offset = Offset { column: 0, row: 0 };
let layers = renderable_layers(&term, &config, &offset);
self.renderer.render(layer_group, render_context, &self.font_set, layers);
// Add new scrollbar thumb.
if let Some((thumb, alpha)) = self.scroll_thumb {
let alpha =
self.scroll_thumb_fade_out.map_or(alpha, |(starting_time, total_duration)| {
let time_elapsed = starting_time.elapsed().min(total_duration).as_secs_f32();
scroll_thumb_fade_out_alpha(total_duration.as_secs_f32(), time_elapsed, alpha)
});
// Linear to sRGB.
let srgb = (alpha.powf(1.0 / 2.2) * 255.0) as u8;
let color = Color { r: srgb, g: srgb, b: srgb, a: (alpha * 255.0) as u8 };
let layer = Layer {
raster: raster_for_rounded_rectangle(&thumb, 2.0, render_context),
clip: None,
style: Style {
fill_rule: FillRule::NonZero,
fill: Fill::Solid(color),
blend_mode: BlendMode::Over,
},
};
layer_group.insert(new_thumb_order, layer);
self.thumb_order = Some(new_thumb_order);
if self.scroll_thumb_fade_out.is_some() {
if let Some(app_sender) = &self.app_sender {
app_sender.request_render(self.view_key);
}
}
}
Ok(())
}
fn handle_message(&mut self, message: Box<dyn Any>) {
if let Some(message) = message.downcast_ref::<TerminalMessages>() {
match message {
TerminalMessages::SetScrollThumbMessage(thumb) => {
if thumb.is_none() {
self.scroll_thumb_fade_out = None;
}
self.scroll_thumb = *thumb;
}
TerminalMessages::SetScrollThumbFadeOutMessage(thumb_fade_out) => {
self.scroll_thumb_fade_out = *thumb_fade_out;
}
}
}
}
fn calculate_size(&self, _: Size) -> Size {
self.size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scroll_thumb_fade_out() {
let alpha: f32 = 1.0;
let fade_out_duration = 1.0;
assert_eq!(scroll_thumb_fade_out_alpha(fade_out_duration, 0.25, alpha), 0.75);
assert_eq!(scroll_thumb_fade_out_alpha(fade_out_duration, 0.50, alpha), 0.5);
assert_eq!(scroll_thumb_fade_out_alpha(fade_out_duration, 0.75, alpha), 0.25);
assert_eq!(scroll_thumb_fade_out_alpha(fade_out_duration, 1.0, alpha), 0.0);
}
}