| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- /* This file is part of DarkFi (https://dark.fi)
- * Copyright (C) 2020-2025 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use async_lock::Mutex as AsyncMutex;
- use async_trait::async_trait;
- use atomic_float::AtomicF32;
- use parking_lot::Mutex as SyncMutex;
- use std::sync::{atomic::Ordering, Arc};
- use crate::{
- gfx::{Point, Rectangle},
- prop::{PropertyAtomicGuard, PropertyFloat32, PropertyPtr, PropertyRect, Role},
- text2::Editor,
- };
- use super::EditorHandle;
- macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::edit::behave", $($arg)*); } }
- pub enum BaseEditType {
- SingleLine,
- MultiLine,
- }
- #[async_trait]
- pub(super) trait EditorBehavior: Send + Sync {
- async fn eval_rect(&self, atom: &mut PropertyAtomicGuard);
- /// Whenever the cursor is modified this MUST be called
- /// to recalculate the scroll value.
- /// Must call redraw after this.
- async fn apply_cursor_scroll(&self, atom: &mut PropertyAtomicGuard);
- fn scroll(&self) -> Point;
- /// Maximum allowed scroll value
- async fn max_scroll(&self) -> f32;
- /// Inner position used for rendering
- fn inner_pos(&self) -> Point;
- fn allow_endl(&self) -> bool;
- fn scroll_ctrl(&self) -> ScrollDir;
- }
- pub(super) enum ScrollDir {
- Vert,
- Horiz,
- }
- impl ScrollDir {
- pub fn cmp(&self, grad: f32) -> bool {
- match self {
- Self::Vert => grad.abs() > 0.5,
- Self::Horiz => grad.abs() < 0.5,
- }
- }
- pub fn travel(&self, start_pos: Point, touch_pos: Point) -> f32 {
- match self {
- Self::Vert => start_pos.y - touch_pos.y,
- Self::Horiz => start_pos.x - touch_pos.x,
- }
- }
- }
- pub(super) struct MultiLine {
- pub min_height: PropertyFloat32,
- pub max_height: PropertyFloat32,
- pub rect: PropertyRect,
- pub baseline: PropertyFloat32,
- pub padding: PropertyPtr,
- pub cursor_descent: PropertyFloat32,
- pub parent_rect: Arc<SyncMutex<Option<Rectangle>>>,
- pub editor: Arc<AsyncMutex<Option<Editor>>>,
- pub content_height: AtomicF32,
- pub scroll: Arc<AtomicF32>,
- }
- impl MultiLine {
- /// Lazy-initializes the editor and returns a handle to it
- async fn lock_editor<'a>(&'a self) -> EditorHandle<'a> {
- EditorHandle { guard: self.editor.lock().await }
- }
- fn bounded_height(&self, height: f32) -> f32 {
- height.clamp(self.min_height.get(), self.max_height.get())
- }
- fn padding_top(&self) -> f32 {
- self.padding.get_f32(0).unwrap()
- }
- fn padding_bottom(&self) -> f32 {
- self.padding.get_f32(1).unwrap()
- }
- /// Gets the real cursor pos within the rect.
- async fn get_cursor_pos(&self) -> Point {
- // This is the position within the content.
- let cursor_pos = self.lock_editor().await.get_cursor_pos();
- // Apply the inner padding
- cursor_pos + self.inner_pos()
- }
- }
- #[async_trait]
- impl EditorBehavior for MultiLine {
- async fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
- let parent_rect = self.parent_rect.lock().clone().unwrap();
- // First we evaluate the width based off the parent dimensions
- self.rect
- .eval_with(
- atom,
- vec![2],
- vec![
- ("parent_w".to_string(), parent_rect.w),
- ("parent_h".to_string(), parent_rect.h),
- ],
- )
- .unwrap();
- let pad_right = self.padding.get_f32(1).unwrap();
- let pad_left = self.padding.get_f32(3).unwrap();
- // Use the width to adjust the height calcs
- let rect_w = self.rect.get_width() - pad_left - pad_right;
- let content_height = {
- let mut editor = self.lock_editor().await;
- editor.set_width(rect_w);
- editor.refresh().await;
- editor.height()
- };
- self.content_height.store(content_height, Ordering::Relaxed);
- let outer_height = content_height + self.padding_top() + self.padding_bottom();
- let rect_h = self.bounded_height(outer_height);
- self.rect.prop().set_f32(atom, Role::Internal, 3, rect_h).unwrap();
- // Finally calculate the position
- self.rect
- .eval_with(
- atom,
- vec![0, 1],
- vec![
- ("parent_w".to_string(), parent_rect.w),
- ("parent_h".to_string(), parent_rect.h),
- ("rect_w".to_string(), rect_w),
- ("rect_h".to_string(), rect_h),
- ],
- )
- .unwrap();
- }
- async fn apply_cursor_scroll(&self, atom: &mut PropertyAtomicGuard) {
- //let pad_top = self.padding_top();
- let pad_bot = self.padding_bottom();
- let mut scroll = self.scroll.load(Ordering::Relaxed);
- let rect_h = self.max_height.get() - pad_bot;
- let cursor_y0 = self.get_cursor_pos().await.y;
- let cursor_h = self.baseline.get() + self.cursor_descent.get();
- // The bottom
- let cursor_y1 = cursor_y0 + cursor_h;
- //t!("apply_cursor_scrolling() cursor = [{cursor_y0}, {cursor_y1}] rect_h={rect_h} scroll={scroll}");
- if cursor_y1 > rect_h + scroll {
- let max_scroll = self.max_scroll().await;
- //t!(" cursor bottom below rect");
- // We want cursor_y1 = rect_h + scroll by adjusting scroll
- scroll = (cursor_y1 - rect_h).clamp(0., max_scroll);
- self.scroll.store(scroll, Ordering::Release);
- } else if cursor_y0 < scroll {
- //t!(" cursor top above rect");
- scroll = cursor_y0.max(0.);
- assert!(scroll >= 0.);
- self.scroll.store(scroll, Ordering::Release);
- }
- }
- fn scroll(&self) -> Point {
- Point::new(0., -self.scroll.load(Ordering::Relaxed))
- }
- /// Maximum allowed scroll value
- /// * `content_height` measures the height of the actual content.
- /// * `outer_height` applies the inner padding.
- /// * `rect_h` then clips the `outer_height` to min/max values.
- /// We only allow scrolling when max clipping has been applied.
- async fn max_scroll(&self) -> f32 {
- let content_height = self.content_height.load(Ordering::Relaxed);
- let outer_height = content_height + self.padding_top() + self.padding_bottom();
- let rect_h = self.rect.get_height();
- //t!("max_scroll content_height={content_height}, rect_h={rect_h}");
- (outer_height - rect_h).max(0.)
- }
- /// Inner position used for rendering
- fn inner_pos(&self) -> Point {
- let pad_top = self.padding_top();
- let pad_bot = self.padding_bottom();
- let pad_left = self.padding.get_f32(3).unwrap();
- let content_height = self.content_height.load(Ordering::Relaxed);
- let outer_height = content_height + pad_top + pad_bot;
- let rect_h = self.rect.get_height();
- let mut inner_pos = Point::zero();
- inner_pos.x = pad_left;
- if outer_height < rect_h {
- // Min was applied to clip. Center content inside the rect.
- inner_pos.y = (rect_h - content_height) / 2.;
- } else {
- inner_pos.y = pad_top;
- }
- inner_pos
- }
- fn allow_endl(&self) -> bool {
- true
- }
- fn scroll_ctrl(&self) -> ScrollDir {
- ScrollDir::Vert
- }
- }
- pub(super) struct SingleLine {
- pub rect: PropertyRect,
- pub padding: PropertyPtr,
- pub cursor_width: PropertyFloat32,
- pub parent_rect: Arc<SyncMutex<Option<Rectangle>>>,
- pub editor: Arc<AsyncMutex<Option<Editor>>>,
- pub content_height: AtomicF32,
- pub scroll: Arc<AtomicF32>,
- }
- impl SingleLine {
- /// Lazy-initializes the editor and returns a handle to it
- async fn lock_editor<'a>(&'a self) -> EditorHandle<'a> {
- EditorHandle { guard: self.editor.lock().await }
- }
- }
- #[async_trait]
- impl EditorBehavior for SingleLine {
- async fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
- let content_height = {
- let mut editor = self.lock_editor().await;
- editor.refresh().await;
- editor.height()
- };
- self.content_height.store(content_height, Ordering::Relaxed);
- let parent_rect = self.parent_rect.lock().clone().unwrap();
- //self.rect.eval(atom, &parent_rect).unwrap();
- self.rect
- .eval_with(
- atom,
- (0..4).collect(),
- vec![
- ("parent_w".to_string(), parent_rect.w),
- ("parent_h".to_string(), parent_rect.h),
- ],
- )
- .unwrap();
- }
- async fn apply_cursor_scroll(&self, atom: &mut PropertyAtomicGuard) {
- let pad_right = self.padding.get_f32(1).unwrap();
- let pad_left = self.padding.get_f32(3).unwrap();
- let mut scroll = self.scroll.load(Ordering::Relaxed);
- let rect_w = self.rect.get_width() - pad_right;
- let cursor_x0 = self.lock_editor().await.get_cursor_pos().x + pad_left;
- let cursor_x1 = cursor_x0 + self.cursor_width.get();
- if cursor_x0 < scroll {
- assert!(cursor_x0 >= 0.);
- scroll = cursor_x0.max(0.);
- self.scroll.store(cursor_x0, Ordering::Release);
- } else if cursor_x1 > rect_w + scroll {
- let max_scroll = self.max_scroll().await;
- let scroll = (cursor_x1 - rect_w).clamp(0., max_scroll);
- self.scroll.store(scroll, Ordering::Release);
- }
- }
- fn scroll(&self) -> Point {
- Point::new(-self.scroll.load(Ordering::Relaxed), 0.)
- }
- async fn max_scroll(&self) -> f32 {
- let pad_right = self.padding.get_f32(1).unwrap();
- let pad_left = self.padding.get_f32(3).unwrap();
- let rect_w = self.rect.get_width();
- let content_w = self.lock_editor().await.width() + self.cursor_width.get();
- (pad_left + pad_right + content_w - rect_w).max(0.)
- }
- fn inner_pos(&self) -> Point {
- let pad_left = self.padding.get_f32(3).unwrap();
- let content_height = self.content_height.load(Ordering::Relaxed);
- let rect_h = self.rect.get_height();
- let mut inner_pos = Point::zero();
- inner_pos.x = pad_left;
- inner_pos.y = (rect_h - content_height) / 2.;
- inner_pos
- }
- fn allow_endl(&self) -> bool {
- false
- }
- fn scroll_ctrl(&self) -> ScrollDir {
- ScrollDir::Horiz
- }
- }
|