use rustc_hash::{FxHashMap, FxHashSet};
use std::any::TypeId;
use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
use std::sync::Arc;
use web_time::Instant;
use crate::app::context::SurfaceMode;
use crate::app::input::command_registry::CommandEntry;
use crate::app::input::command_registry::CommandRegistry;
use crate::callback::{CancellationToken, CommandLink, CommandTx, Dispatcher, Link, ScopeId};
use crate::core::context_value::ContextValue;
use crate::core::element::{Element, Key};
use crate::core::event::KeyEvent;
use crate::core::node::{NodeId, NodeKind, NodeTree};
use crate::core::runtime_env::{
CopyFeedbackRequest, DevToolsRequest, MemoDependency, MemoDependencySnapshot, RuntimeEnv,
ScrollDependency, ScrollDependencyKind, ScrollIdentity, TranscriptEntry,
};
use crate::runtime::FocusRequest;
use crate::style::{HostTerminalColors, Rect, RichText, Theme, ThemeExtension};
use crate::utils::GridSelection;
#[non_exhaustive]
pub struct Command {
action: Box<dyn CommandAction>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TaskPolicy {
QueueAll,
DropIfRunning,
LatestOnly,
}
mod task_policy;
use task_policy::Task;
#[cfg(not(target_arch = "wasm32"))]
mod executor_native;
#[cfg(target_arch = "wasm32")]
mod executor_wasm;
#[cfg(not(target_arch = "wasm32"))]
mod timer_native;
#[cfg(target_arch = "wasm32")]
mod timer_wasm;
#[cfg(not(target_arch = "wasm32"))]
use executor_native::TaskExecutor;
#[cfg(target_arch = "wasm32")]
use executor_wasm::TaskExecutor;
#[cfg(not(target_arch = "wasm32"))]
use timer_native::TimerService;
#[cfg(target_arch = "wasm32")]
use timer_wasm::TimerService;
pub(crate) fn advance_deferred_commands(horizon: Instant, runtime_id: RuntimeId) -> usize {
TimerService::global().advance_owned(horizon, runtime_id)
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Command").finish_non_exhaustive()
}
}
impl Command {
pub fn new(action: impl FnOnce() + 'static) -> Self {
Self {
action: Box::new(RunAction(Some(action))),
}
}
pub fn spawn<Msg, F>(f: F) -> Self
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
Self {
action: Box::new(SpawnAction::<Msg, F> {
f: Some(f),
_marker: PhantomData,
}),
}
}
pub fn after<Msg, F>(delay: std::time::Duration, f: F) -> Self
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
Self {
action: Box::new(AfterAction::<Msg, F> {
delay,
f: Some(f),
_marker: PhantomData,
}),
}
}
pub fn spawn_keyed<Msg, F>(key: impl Into<Arc<str>>, policy: TaskPolicy, f: F) -> Self
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
Self {
action: Box::new(SpawnKeyedAction::<Msg, F> {
key: key.into(),
policy,
f: Some(f),
_marker: PhantomData,
}),
}
}
pub(crate) fn run(self, runtime: CommandRuntime) {
self.action.run(runtime);
}
}
pub(crate) struct CommandRuntime {
pub(crate) scope: ScopeId,
pub(crate) tx: CommandTx,
pub(crate) runtime_id: RuntimeId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct RuntimeId(u64);
impl RuntimeId {
#[cfg(test)]
pub(crate) fn from_raw_for_tests(raw: u64) -> Self {
Self(raw)
}
pub(crate) fn next() -> Self {
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
Self(NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
}
}
trait CommandAction {
fn run(self: Box<Self>, runtime: CommandRuntime);
}
struct RunAction<F>(Option<F>);
impl<F> CommandAction for RunAction<F>
where
F: FnOnce() + 'static,
{
fn run(mut self: Box<Self>, _runtime: CommandRuntime) {
if let Some(f) = self.0.take() {
f();
}
}
}
struct SpawnAction<Msg, F> {
f: Option<F>,
_marker: PhantomData<fn(Msg)>,
}
struct SpawnKeyedAction<Msg, F> {
key: Arc<str>,
policy: TaskPolicy,
f: Option<F>,
_marker: PhantomData<fn(Msg)>,
}
pub(crate) fn schedule_after(delay: std::time::Duration, f: impl FnOnce() + Send + 'static) {
TimerService::global().schedule(delay, Task::with_token(f, CancellationToken::default()));
}
struct AfterAction<Msg, F> {
delay: std::time::Duration,
f: Option<F>,
_marker: PhantomData<fn(Msg)>,
}
impl<Msg, F> CommandAction for AfterAction<Msg, F>
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
fn run(mut self: Box<Self>, runtime: CommandRuntime) {
let Some(f) = self.f.take() else {
return;
};
let token = CancellationToken::default();
let link = CommandLink::new(runtime.scope, runtime.tx, token.clone());
TimerService::global().schedule_owned(
self.delay,
Task::with_token(move || f(link), token),
Some(runtime.runtime_id),
);
}
}
impl<Msg, F> CommandAction for SpawnAction<Msg, F>
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
fn run(mut self: Box<Self>, runtime: CommandRuntime) {
let Some(f) = self.f.take() else {
return;
};
let token = CancellationToken::default();
let link = CommandLink::new(runtime.scope, runtime.tx, token.clone());
TaskExecutor::global().execute(Task::with_token(move || f(link), token));
}
}
impl<Msg, F> CommandAction for SpawnKeyedAction<Msg, F>
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
fn run(mut self: Box<Self>, runtime: CommandRuntime) {
let Some(f) = self.f.take() else {
return;
};
let key = Arc::clone(&self.key);
let policy = self.policy;
let token = CancellationToken::default();
let link = CommandLink::new(runtime.scope, runtime.tx, token.clone());
TaskExecutor::global().execute_keyed(key, policy, Task::with_token(move || f(link), token));
}
}
impl<Msg: 'static> Link<Msg> {
pub fn command<F>(&self, f: F) -> Command
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
Command::spawn::<Msg, F>(f)
}
pub fn command_keyed<F>(&self, key: impl Into<Arc<str>>, policy: TaskPolicy, f: F) -> Command
where
Msg: Send + 'static,
F: FnOnce(CommandLink<Msg>) + Send + 'static,
{
Command::spawn_keyed::<Msg, F>(key, policy, f)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum UpdateLevel {
#[default]
None,
Paint,
Layout,
Full,
}
pub struct Update {
pub dirty: bool,
pub(crate) level: UpdateLevel,
pub command: Option<Command>,
}
impl Update {
pub fn paint() -> Self {
Self {
dirty: true,
level: UpdateLevel::Paint,
command: None,
}
}
pub fn layout() -> Self {
Self {
dirty: true,
level: UpdateLevel::Layout,
command: None,
}
}
pub fn layout_with_command(command: impl Into<Option<Command>>) -> Self {
match command.into() {
Some(command) => Self {
dirty: true,
level: UpdateLevel::Layout,
command: Some(command),
},
None => Self::layout(),
}
}
pub fn full() -> Self {
Self {
dirty: true,
level: UpdateLevel::Full,
command: None,
}
}
pub fn command_only(command: Command) -> Self {
Self {
dirty: false,
level: UpdateLevel::None,
command: Some(command),
}
}
pub fn none() -> Self {
Self {
dirty: false,
level: UpdateLevel::None,
command: None,
}
}
pub fn with_command(command: impl Into<Option<Command>>) -> Self {
match command.into() {
Some(cmd) => Self {
dirty: true,
level: UpdateLevel::Full,
command: Some(cmd),
},
None => Self::full(),
}
}
pub fn level(&self) -> UpdateLevel {
self.level
}
}
pub struct KeyUpdate {
pub handled: bool,
pub update: Update,
}
impl KeyUpdate {
pub fn handled(update: Update) -> Self {
Self {
handled: true,
update,
}
}
pub fn unhandled(update: Update) -> Self {
Self {
handled: false,
update,
}
}
}
pub trait Component: Sized + 'static {
type Message: 'static;
type Properties: Clone + PartialEq + 'static;
type State: 'static;
fn create_state(&self, props: &Self::Properties) -> Self::State;
fn memo_key(&self, _props: &Self::Properties, _ctx: &Context<Self>) -> Option<u64> {
None
}
fn init(&mut self, _ctx: &mut Context<Self>) -> Option<Command> {
None
}
fn view(&self, ctx: &Context<Self>) -> Element;
fn on_key(&mut self, _key: KeyEvent, _ctx: &mut Context<Self>) -> KeyUpdate {
KeyUpdate::unhandled(Update::none())
}
fn on_window_focus_changed(&mut self, _focused: bool, _ctx: &mut Context<Self>) -> Update {
Update::none()
}
fn update(&mut self, msg: Self::Message, ctx: &mut Context<Self>) -> Update;
fn on_props_changed(
&mut self,
_old_props: &Self::Properties,
_ctx: &mut Context<Self>,
) -> Update {
Update::none()
}
fn unmount(&mut self, _ctx: &mut Context<Self>) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Breakpoint {
Small,
Medium,
Large,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ScrollbarVisibility {
pub v: bool,
pub h: bool,
}
#[derive(Default)]
struct NodeChainState {
current_node: Cell<Option<NodeId>>,
chain_scopes: RefCell<Vec<ScopeId>>,
chain_keys: RefCell<Vec<Key>>,
generation: Cell<u64>,
}
impl NodeChainState {
fn node_id(&self) -> Option<NodeId> {
self.current_node.get()
}
fn has_within_scope(&self, scope: ScopeId) -> bool {
self.chain_scopes.borrow().contains(&scope)
}
fn has_within_key(&self, key: &Key) -> bool {
self.chain_keys
.borrow()
.iter()
.any(|candidate| candidate == key)
}
fn push_scope_if_missing(scopes: &mut Vec<ScopeId>, scope: ScopeId) {
if !scopes.contains(&scope) {
scopes.push(scope);
}
}
fn push_key_if_missing(keys: &mut Vec<Key>, key: &Key) {
if !keys.iter().any(|candidate| candidate == key) {
keys.push(key.clone());
}
}
fn replace_snapshot(&self, node: Option<NodeId>, scopes: Vec<ScopeId>, keys: Vec<Key>) {
let mut scope_ref = self.chain_scopes.borrow_mut();
let mut key_ref = self.chain_keys.borrow_mut();
let changed = self.current_node.get() != node || *scope_ref != scopes || *key_ref != keys;
self.current_node.set(node);
*scope_ref = scopes;
*key_ref = keys;
if changed {
self.generation
.set(self.generation.get().wrapping_add(1).max(1));
}
}
fn chain_for(tree: &NodeTree, node: Option<NodeId>) -> (Vec<ScopeId>, Vec<Key>) {
let mut scopes = Vec::new();
let mut keys = Vec::new();
if let Some(mut cur) = node {
Self::push_scope_if_missing(&mut scopes, ScopeId(1));
loop {
if !tree.is_valid(cur) {
break;
}
let node_ref = tree.node(cur);
if let Some(k) = &node_ref.key {
Self::push_key_if_missing(&mut keys, k);
}
if let NodeKind::Group(group) = &node_ref.kind {
Self::push_scope_if_missing(&mut scopes, group.scope);
}
let Some(parent) = node_ref.parent else {
break;
};
cur = parent;
}
}
(scopes, keys)
}
fn update_chain(&self, tree: &NodeTree, node: Option<NodeId>) {
let (scopes, keys) = Self::chain_for(tree, node);
self.replace_snapshot(node, scopes, keys);
}
fn generation(&self) -> u64 {
self.generation.get()
}
}
#[derive(Default)]
pub(crate) struct FocusContext {
inner: NodeChainState,
}
impl FocusContext {
pub(crate) fn update_from_tree(
&self,
tree: &NodeTree,
focused: Option<NodeId>,
focused_key: Option<&Key>,
) {
let mut cur = focused.filter(|id| tree.is_valid(*id));
if cur.is_none()
&& let Some(key) = focused_key
{
if let Some(id) = tree
.iter()
.find(|n| n.key.as_ref() == Some(key))
.map(|n| n.id)
{
cur = Some(id);
} else {
self.inner
.replace_snapshot(None, Vec::new(), vec![key.clone()]);
return;
}
}
self.inner.update_chain(tree, cur);
}
pub(crate) fn focused_node_id(&self) -> Option<NodeId> {
self.inner.node_id()
}
pub(crate) fn has_focus_within_scope(&self, scope: ScopeId) -> bool {
self.inner.has_within_scope(scope)
}
pub(crate) fn has_focus_within_key(&self, key: &Key) -> bool {
self.inner.has_within_key(key)
}
pub(crate) fn generation(&self) -> u64 {
self.inner.generation()
}
}
#[derive(Default)]
pub(crate) struct HoverContext {
inner: NodeChainState,
queries: RefCell<Vec<(ScopeId, HoverQuery)>>,
}
#[derive(Clone, PartialEq, Eq)]
enum HoverQuery {
WithinScope(ScopeId),
WithinKey(Key),
NodeId,
}
impl HoverContext {
pub(crate) fn update_from_tree(&self, tree: &NodeTree, hovered: Option<NodeId>) {
self.queries.borrow_mut().clear();
self.update_chain(tree, hovered);
}
pub(crate) fn update_chain(&self, tree: &NodeTree, hovered: Option<NodeId>) {
let cur = hovered.filter(|id| tree.is_valid(*id));
self.inner.update_chain(tree, cur);
}
pub(crate) fn begin_scoped_view(
&self,
tree: &NodeTree,
hovered: Option<NodeId>,
scopes: &[ScopeId],
) {
self.queries
.borrow_mut()
.retain(|(asker, _)| !scopes.contains(asker));
self.update_chain(tree, hovered);
}
fn record(&self, asker: ScopeId, query: HoverQuery) {
let mut queries = self.queries.borrow_mut();
if !queries
.iter()
.any(|(scope, recorded)| *scope == asker && *recorded == query)
{
queries.push((asker, query));
}
}
pub(crate) fn hovered_node_id(&self, asker: ScopeId) -> Option<NodeId> {
self.record(asker, HoverQuery::NodeId);
self.inner.node_id()
}
pub(crate) fn has_hover_within_scope(&self, scope: ScopeId) -> bool {
self.record(scope, HoverQuery::WithinScope(scope));
self.inner.has_within_scope(scope)
}
pub(crate) fn has_hover_within_key(&self, asker: ScopeId, key: &Key) -> bool {
self.record(asker, HoverQuery::WithinKey(key.clone()));
self.inner.has_within_key(key)
}
pub(crate) fn scopes_needing_view(
&self,
tree: &NodeTree,
hovered: Option<NodeId>,
) -> Vec<ScopeId> {
let queries = self.queries.borrow();
if queries.is_empty() {
return Vec::new();
}
let cur = hovered.filter(|id| tree.is_valid(*id));
let (scopes, keys) = NodeChainState::chain_for(tree, cur);
let mut affected = Vec::new();
for (asker, query) in queries.iter() {
let changed = match query {
HoverQuery::WithinScope(scope) => {
scopes.contains(scope) != self.inner.has_within_scope(*scope)
}
HoverQuery::WithinKey(key) => {
keys.iter().any(|candidate| candidate == key) != self.inner.has_within_key(key)
}
HoverQuery::NodeId => self.inner.node_id() != cur,
};
if changed && !affected.contains(asker) {
affected.push(*asker);
}
}
affected
}
pub(crate) fn generation(&self) -> u64 {
self.inner.generation()
}
}
#[derive(Default)]
pub(crate) struct ScrollContext {
by_key: RefCell<FxHashMap<ScrollIdentity, ScrollbarVisibility>>,
text_area_metrics_by_key: RefCell<FxHashMap<ScrollIdentity, crate::widgets::TextAreaMetrics>>,
metrics_generations: RefCell<FxHashMap<ScrollIdentity, u64>>,
scrollbar_generations: RefCell<FxHashMap<ScrollIdentity, u64>>,
metrics_view_dependencies: RefCell<FxHashSet<ScrollIdentity>>,
scrollbar_view_dependencies: RefCell<FxHashSet<ScrollIdentity>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ScrollGenerations {
metrics: FxHashMap<ScrollIdentity, u64>,
scrollbars: FxHashMap<ScrollIdentity, u64>,
}
impl ScrollContext {
pub(crate) fn update_from_tree(&self, tree: &NodeTree) {
let mut map = self.by_key.borrow_mut();
let prev = std::mem::take(&mut *map);
let mut metrics_map = self.text_area_metrics_by_key.borrow_mut();
let prev_metrics = std::mem::take(&mut *metrics_map);
for node in tree.iter_with_overlays() {
if let (Some(key), NodeKind::TextArea(text_area)) = (&node.key, &node.kind) {
let identity = ScrollIdentity {
scope: node_scope(tree, node.id),
key: key.clone(),
};
let metrics = text_area.metrics(node.rect);
map.insert(identity.clone(), metrics.scrollbars);
metrics_map.insert(identity, metrics);
}
}
advance_changed_generations(&prev, &map, &self.scrollbar_generations);
advance_changed_generations(&prev_metrics, &metrics_map, &self.metrics_generations);
}
pub(crate) fn get(&self, identity: &ScrollIdentity) -> Option<ScrollbarVisibility> {
self.by_key.borrow().get(identity).copied()
}
pub(crate) fn text_area_metrics(
&self,
identity: &ScrollIdentity,
) -> Option<crate::widgets::TextAreaMetrics> {
self.text_area_metrics_by_key
.borrow()
.get(identity)
.cloned()
}
pub(crate) fn begin_view(&self, scope: ScopeId) {
self.metrics_view_dependencies
.borrow_mut()
.retain(|identity| identity.scope != scope);
self.scrollbar_view_dependencies
.borrow_mut()
.retain(|identity| identity.scope != scope);
}
pub(crate) fn remove_scope(&self, scope: ScopeId) {
self.begin_view(scope);
self.by_key
.borrow_mut()
.retain(|identity, _| identity.scope != scope);
self.text_area_metrics_by_key
.borrow_mut()
.retain(|identity, _| identity.scope != scope);
self.metrics_generations
.borrow_mut()
.retain(|identity, _| identity.scope != scope);
self.scrollbar_generations
.borrow_mut()
.retain(|identity, _| identity.scope != scope);
}
pub(crate) fn mark_view_dependency(&self, dependency: &ScrollDependency) {
match dependency.kind {
ScrollDependencyKind::Metrics => {
self.metrics_view_dependencies
.borrow_mut()
.insert(dependency.identity.clone());
}
ScrollDependencyKind::Scrollbars => {
self.scrollbar_view_dependencies
.borrow_mut()
.insert(dependency.identity.clone());
}
}
}
pub(crate) fn dependency_generation(&self, dependency: &ScrollDependency) -> u64 {
let generations = match dependency.kind {
ScrollDependencyKind::Metrics => &self.metrics_generations,
ScrollDependencyKind::Scrollbars => &self.scrollbar_generations,
};
generations
.borrow()
.get(&dependency.identity)
.copied()
.unwrap_or(0)
}
pub(crate) fn view_generations(&self) -> ScrollGenerations {
ScrollGenerations {
metrics: self.metrics_generations.borrow().clone(),
scrollbars: self.scrollbar_generations.borrow().clone(),
}
}
pub(crate) fn view_dependencies_stale(&self, prev: &ScrollGenerations) -> bool {
self.metrics_view_dependencies
.borrow()
.iter()
.any(|identity| {
self.metrics_generations
.borrow()
.get(identity)
.copied()
.unwrap_or(0)
!= prev.metrics.get(identity).copied().unwrap_or(0)
})
|| self
.scrollbar_view_dependencies
.borrow()
.iter()
.any(|identity| {
self.scrollbar_generations
.borrow()
.get(identity)
.copied()
.unwrap_or(0)
!= prev.scrollbars.get(identity).copied().unwrap_or(0)
})
}
}
fn node_scope(tree: &NodeTree, mut id: NodeId) -> ScopeId {
loop {
let node = tree.node(id);
if let NodeKind::Group(group) = &node.kind {
return group.scope;
}
let Some(parent) = node.parent.filter(|parent| tree.is_valid(*parent)) else {
return ScopeId(1);
};
id = parent;
}
}
fn advance_changed_generations<T: PartialEq>(
previous: &FxHashMap<ScrollIdentity, T>,
current: &FxHashMap<ScrollIdentity, T>,
generations: &RefCell<FxHashMap<ScrollIdentity, u64>>,
) {
let identities: FxHashSet<_> = previous.keys().chain(current.keys()).cloned().collect();
let mut generations = generations.borrow_mut();
for identity in identities {
if previous.get(&identity) != current.get(&identity) {
let generation = generations.entry(identity).or_default();
*generation = generation.wrapping_add(1).max(1);
}
}
generations.retain(|identity, _| current.contains_key(identity));
}
pub struct Context<C: Component> {
pub state: C::State,
pub props: C::Properties,
viewport: Rect,
link: Link<C::Message>,
env: RuntimeEnv,
scope: ScopeId,
}
impl<C: Component> Context<C> {
pub(crate) fn new(
component: &C,
scope: ScopeId,
dispatcher: Dispatcher,
props: C::Properties,
env: RuntimeEnv,
viewport: Rect,
) -> Self {
let state = component.create_state(&props);
Self {
state,
props,
viewport,
link: Link::new(scope, dispatcher),
env,
scope,
}
}
pub fn link(&self) -> &Link<C::Message> {
&self.link
}
pub fn toast(&self) -> crate::overlay::ToastHandle {
crate::overlay::ToastHandle::new(self.env.overlay_manager.clone())
}
pub fn clipboard(&self) -> crate::clipboard::ClipboardHandle {
crate::clipboard::ClipboardHandle::new(
self.env.clipboard.clone(),
self.env.clipboard_config.clone(),
)
}
pub fn command_registry(&self) -> CommandRegistry {
self.env.command_registry.clone()
}
pub fn command_chord_pending(&self) -> bool {
self.env.command_chord_pending_since.get().is_some()
}
pub fn command_chord_pending_since(&self) -> Option<Instant> {
self.env.command_chord_pending_since.get()
}
pub fn set_command_chord_reveal_delay(&self, delay: std::time::Duration) {
self.env.command_chord_reveal_delay.set(delay);
}
pub fn command_chord_revealed(&self) -> bool {
self.env
.command_chord_pending_since
.get()
.is_some_and(|since| {
self.env.elapsed(since) >= self.env.command_chord_reveal_delay.get()
})
}
pub fn register_command(&self, entry: CommandEntry) {
self.env
.command_registry
.register_for_scope(self.scope, entry);
}
pub fn viewport(&self) -> Rect {
self.env.note_memo_dependency(MemoDependency::Viewport);
self.viewport
}
pub fn theme(&self) -> Theme {
self.env.note_memo_dependency(MemoDependency::Theme);
self.env.active_theme.borrow().clone()
}
pub fn theme_extension<T>(&self) -> Option<T>
where
T: ThemeExtension,
{
self.env.note_memo_dependency(MemoDependency::Theme);
self.env.active_theme.borrow().extension_cloned::<T>()
}
pub fn use_context<T>(&self) -> Option<T>
where
T: ContextValue,
{
self.env.note_memo_dependency(MemoDependency::Context {
type_id: TypeId::of::<T>(),
name: std::any::type_name::<T>(),
});
self.env
.contexts
.borrow()
.get(&TypeId::of::<T>())
.and_then(|value| value.as_ref().downcast_ref::<T>())
.cloned()
}
pub fn context<T>(&self) -> Option<T>
where
T: ContextValue,
{
self.use_context::<T>()
}
pub fn is_inline(&self) -> bool {
self.env.surface_mode.is_inline()
}
pub fn surface_mode(&self) -> SurfaceMode {
self.env.surface_mode
}
pub fn effect_phase(&self) -> u64 {
self.env.effect_phase.get()
}
pub fn host_terminal_colors(&self) -> Option<HostTerminalColors> {
self.env.host_terminal_colors()
}
pub fn host_terminal_color_generation(&self) -> u64 {
self.env.host_terminal_color_generation()
}
pub fn request_host_terminal_color_refresh(&self) {
self.env.request_host_terminal_color_refresh();
}
pub fn last_mouse(&self) -> Option<(u16, u16)> {
self.env.last_mouse.get()
}
pub fn mouse_capture_enabled(&self) -> bool {
self.env.note_memo_dependency(MemoDependency::MouseCapture);
self.env.mouse_capture.get()
}
pub fn set_mouse_capture(&self, enabled: bool) {
if self.env.mouse_capture.get() != enabled {
self.env.mouse_capture.set(enabled);
self.env.mouse_capture_generation.set(
self.env
.mouse_capture_generation
.get()
.wrapping_add(1)
.max(1),
);
}
}
pub fn toggle_mouse_capture(&self) -> bool {
let next = !self.env.mouse_capture.get();
self.set_mouse_capture(next);
next
}
pub fn suspend_to_shell(&self) {
crate::app::job_control::request_suspend();
}
pub fn append_transcript_lines<I, L>(&mut self, lines: I)
where
I: IntoIterator<Item = L>,
L: Into<RichText>,
{
if !matches!(self.env.surface_mode, SurfaceMode::InlineTranscript { .. }) {
return;
}
let lines: Vec<RichText> = lines.into_iter().map(Into::into).collect();
if lines.is_empty() {
return;
}
self.env
.transcript_history
.borrow_mut()
.push(TranscriptEntry::Lines(lines.clone()));
self.env
.pending_transcript_entries
.borrow_mut()
.push_back(TranscriptEntry::Lines(lines));
}
pub fn append_transcript_element(&mut self, element: impl Into<Element>) {
if !matches!(self.env.surface_mode, SurfaceMode::InlineTranscript { .. }) {
return;
}
let element = element.into();
if element.contains_unexpanded_component() {
crate::debug::internal_log!(
"[tui-lipan] append_transcript_element ignored an element containing Component nodes"
);
return;
}
self.env
.transcript_history
.borrow_mut()
.push(TranscriptEntry::Element(Box::new(element.clone())));
self.env
.pending_transcript_entries
.borrow_mut()
.push_back(TranscriptEntry::Element(Box::new(element)));
}
pub fn has_focus_within(&self) -> bool {
self.env.note_memo_dependency(MemoDependency::Focus);
self.env.focus.has_focus_within_scope(self.scope)
}
pub fn has_focus_within_key(&self, key: impl Into<Key>) -> bool {
let key = key.into();
self.env.note_memo_dependency(MemoDependency::Focus);
self.env.focus.has_focus_within_key(&key)
}
pub fn text_area_scrollbars(&self, key: impl Into<Key>) -> ScrollbarVisibility {
let dependency = ScrollDependency {
identity: ScrollIdentity {
scope: self.scope,
key: key.into(),
},
kind: ScrollDependencyKind::Scrollbars,
};
self.env
.note_memo_dependency(MemoDependency::Scroll(dependency.clone()));
self.env.scroll.mark_view_dependency(&dependency);
self.env
.scroll
.text_area_metrics(&dependency.identity)
.map(|metrics| metrics.scrollbars)
.or_else(|| self.env.scroll.get(&dependency.identity))
.unwrap_or_default()
}
pub fn text_area_metrics(
&self,
key: impl Into<Key>,
) -> Option<crate::widgets::TextAreaMetrics> {
let dependency = ScrollDependency {
identity: ScrollIdentity {
scope: self.scope,
key: key.into(),
},
kind: ScrollDependencyKind::Metrics,
};
self.env
.note_memo_dependency(MemoDependency::Scroll(dependency.clone()));
self.env.scroll.mark_view_dependency(&dependency);
self.env.scroll.text_area_metrics(&dependency.identity)
}
pub fn has_hover_within(&self) -> bool {
self.env.note_memo_dependency(MemoDependency::Hover);
self.env.hover.has_hover_within_scope(self.scope)
}
pub fn has_hover_within_key(&self, key: impl Into<Key>) -> bool {
let key = key.into();
self.env.note_memo_dependency(MemoDependency::Hover);
self.env.hover.has_hover_within_key(self.scope, &key)
}
pub fn focused_node_id(&self) -> Option<NodeId> {
self.env.note_memo_dependency(MemoDependency::Focus);
self.env.focus.focused_node_id()
}
pub fn hovered_node_id(&self) -> Option<NodeId> {
self.env.note_memo_dependency(MemoDependency::Hover);
self.env.hover.hovered_node_id(self.scope)
}
pub fn transition<T>(
&self,
key: impl Into<Key>,
target: T,
config: crate::animation::TransitionConfig,
) -> T
where
T: crate::animation::Lerp + PartialEq + 'static,
{
let key = key.into();
self.env.note_memo_dependency(MemoDependency::Transition);
self.env.animations.transition(key, target, config)
}
pub fn animated_color(
&self,
key: impl Into<Key>,
target: crate::style::Color,
config: crate::animation::TransitionConfig,
) -> crate::style::Paint {
self.env
.animations
.animated_paint(key.into(), target, config)
}
pub fn breakpoint(&self, medium: u16, large: u16) -> Breakpoint {
let (medium, large) = if medium <= large {
(medium, large)
} else {
(large, medium)
};
self.env.note_memo_dependency(MemoDependency::Viewport);
let w = self.viewport.w;
if w < medium {
Breakpoint::Small
} else if w < large {
Breakpoint::Medium
} else {
Breakpoint::Large
}
}
pub(crate) fn env(&self) -> &RuntimeEnv {
&self.env
}
pub(crate) fn set_viewport(&mut self, viewport: Rect) {
self.viewport = viewport;
}
pub(crate) fn set_active_theme(&mut self, theme: Theme) {
let mut active_theme = self.env.active_theme.borrow_mut();
if *active_theme != theme {
*active_theme = theme;
self.env.active_theme_generation.set(
self.env
.active_theme_generation
.get()
.wrapping_add(1)
.max(1),
);
}
}
pub(crate) fn set_contexts(
&mut self,
contexts: rustc_hash::FxHashMap<TypeId, std::sync::Arc<dyn std::any::Any>>,
generations: rustc_hash::FxHashMap<TypeId, u64>,
) {
*self.env.contexts.borrow_mut() = contexts;
*self.env.context_generations.borrow_mut() = generations;
}
pub(crate) fn memo_key(&self, component: &C) -> Option<u64> {
component.memo_key(&self.props, self)
}
pub(crate) fn begin_memo_dependency_capture(&self) {
self.env.scroll.begin_view(self.scope);
self.env.begin_memo_dependency_capture();
}
pub(crate) fn finish_memo_dependency_capture(&self) -> MemoDependencySnapshot {
self.env.finish_memo_dependency_capture(self.viewport)
}
pub(crate) fn memo_dependencies_match(&self, snapshot: &MemoDependencySnapshot) -> bool {
snapshot.matches(&self.env, self.viewport)
}
#[cfg(feature = "devtools")]
pub(crate) fn memo_dependency_mismatch(
&self,
snapshot: &MemoDependencySnapshot,
) -> Option<crate::core::nested::MemoDependencyKind> {
snapshot.first_mismatch(&self.env, self.viewport)
}
pub fn quit(&mut self) {
self.env.quit.set(true);
}
pub(crate) fn request_quit(&self) {
self.env.quit.set(true);
}
pub fn request_focus(&mut self, key: impl Into<Key>) {
*self.env.focus_request.borrow_mut() = Some(FocusRequest::Key(key.into()));
}
pub fn blur(&mut self) {
*self.env.focus_request.borrow_mut() = Some(FocusRequest::Clear);
}
pub fn focus_next(&mut self) {
*self.env.focus_request.borrow_mut() = Some(FocusRequest::Next);
}
pub fn focus_prev(&mut self) {
*self.env.focus_request.borrow_mut() = Some(FocusRequest::Prev);
}
pub fn request_full_repaint(&self) {
self.env.full_repaint.set(true);
}
pub fn flash_copy_feedback(&self, node_id: NodeId) {
self.env.request_copy_feedback(node_id, None);
}
pub fn flash_copy_feedback_range(&self, node_id: NodeId, range: GridSelection) {
self.env.request_copy_feedback(node_id, Some(range));
}
pub fn set_devtools_metrics<F, I>(&self, metrics: F)
where
F: FnOnce() -> I,
I: IntoIterator<Item = crate::DevToolsMetric>,
{
#[cfg(feature = "devtools")]
{
self.env
.devtools_metrics
.replace(metrics().into_iter().collect());
}
#[cfg(not(feature = "devtools"))]
{
let _ = metrics;
}
}
#[cfg(feature = "devtools")]
pub(crate) fn devtools_metrics(
&self,
) -> std::rc::Rc<crate::core::runtime_env::DevToolsMetrics> {
std::rc::Rc::clone(&self.env.devtools_metrics)
}
pub fn devtools_visible(&self) -> bool {
#[cfg(feature = "devtools")]
{
self.env.devtools_metrics.is_visible()
}
#[cfg(not(feature = "devtools"))]
{
false
}
}
pub fn show_devtools(&self) {
*self.env.devtools_request.borrow_mut() = Some(DevToolsRequest::Show);
}
pub fn hide_devtools(&self) {
*self.env.devtools_request.borrow_mut() = Some(DevToolsRequest::Hide);
}
pub fn toggle_devtools(&self) {
*self.env.devtools_request.borrow_mut() = Some(DevToolsRequest::Toggle);
}
pub(crate) fn take_focus_request(&self) -> Option<FocusRequest> {
self.env.focus_request.borrow_mut().take()
}
pub(crate) fn take_full_repaint_request(&self) -> bool {
self.env.full_repaint.replace(false)
}
pub(crate) fn take_copy_feedback_requests(&self) -> Vec<CopyFeedbackRequest> {
self.env.take_copy_feedback_requests()
}
pub(crate) fn take_devtools_request(&self) -> Option<DevToolsRequest> {
self.env.devtools_request.borrow_mut().take()
}
pub fn request_ui_snapshot_to(&self, path: impl AsRef<std::path::Path>) {
let path = path.as_ref().to_path_buf();
let format = crate::ui_snapshot::UiSnapshotFileFormat::from_path(&path);
*self.env.ui_snapshot_request.borrow_mut() =
Some(crate::ui_snapshot::UiSnapshotRequest::Write { path, format });
self.request_full_repaint();
}
pub fn request_ui_snapshot_to_slot(&self, slot: &crate::ui_snapshot::UiSnapshotSlot) {
*self.env.ui_snapshot_request.borrow_mut() = Some(
crate::ui_snapshot::UiSnapshotRequest::Deliver(slot.shared()),
);
self.request_full_repaint();
}
pub(crate) fn take_ui_snapshot_request(&self) -> Option<crate::ui_snapshot::UiSnapshotRequest> {
self.env.ui_snapshot_request.borrow_mut().take()
}
pub(crate) fn should_quit(&self) -> bool {
self.env.quit.get()
}
}
#[cfg(test)]
mod tests;