iced icon indicating copy to clipboard operation
iced copied to clipboard

Implement tinyskia text bounds

Open DKolter opened this issue 11 months ago • 8 comments

Fixes #2740

Paragraph clip bounds are currently marked todo in the tiny skia backend. This PR makes an effort to change this by:

  1. Finding the intersection between the clip_bounds argument (layer bounds) and the clip_bounds of the paragraph
  2. Adjusting the clip mask to the intersection bounds if the physical bounds extend past the clip_bounds, otherwise don't update the clip mask to avoid expensive clip mask drawing.

The original issue only mentions text_input, however text_editor and cached text have the same todo comment and are also handled by this PR.

Before

grafik

After

grafik

DKolter avatar May 04 '25 15:05 DKolter

This looks like it also fixes #2851 <3

lufte avatar May 15 '25 16:05 lufte

After more testing, though, I found a bug which is hard to explain. You can reproduce it by cloning my app from this repo https://git.sr.ht/~lufte/vimini/tree/tiny-skia-clipping (make sure you're on the tiny-skia-clipping branch), running it with tiny-skia, and scrolling down on the default page that opens up. You should notice an increasingly bigger rectangle, same color as the background, that is drawn on top of the text at the top. The tabs in the navigation bar at the top are using the new feature.

Let me know if it's hard to reproduce and I can provide assistance.

lufte avatar May 15 '25 20:05 lufte

Hey @lufte, I had issues reproducing the visual bug. Do you think you could try to reproduce it based on this branch? If not, a short video would also be a great start. Thank you for checking the PR out!

DKolter avatar May 25 '25 11:05 DKolter

@DKolter I was able to write a short program that shows the behavior, code below. Just run it with tiny-skia and scroll down, you should see how less and less text is shown.

main.rs

#[derive(Default)]
pub struct App {
}

#[derive(Debug)]
pub enum Message {
}

impl App {
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        let mut command = iced::Task::none();
        return command;
    }

    fn view(&self) -> iced::Element<Message> {
        let mut tab_bar = iced::widget::Row::new().width(iced::Length::Fill);
        tab_bar = tab_bar.push(
            iced::widget::Container::new(
                iced::widget::Text::new("Tab 1")
                .wrapping(iced::widget::text::Wrapping::None)
            )
            .width(iced::Length::Fill)
            .padding(2)
            .clip(true)
        );
        tab_bar = tab_bar.push(
            iced::widget::Container::new(
                iced::widget::Text::new("Tab 2")
                .wrapping(iced::widget::text::Wrapping::None)
            )
            .width(iced::Length::Fill)
            .padding(2)
            .clip(true)
        );
        let mut tabbed_browser = iced::widget::Column::new();
        tabbed_browser = tabbed_browser.push(tab_bar);
        let mut text_column = iced::widget::Column::new();

        for _ in [0; 100] {
            text_column = text_column.push(
                iced::widget::Text::new("A cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by Elm.")
                .size(17)
                .shaping(iced::advanced::text::Shaping::Advanced)
            );
        }
        let document_viewer = iced::widget::Container::new(
            iced::widget::scrollable::Scrollable::new(
                iced::widget::Container::new(
                    text_column
                    .max_width(800)
                    .width(iced::Length::Fill)
                    .padding(10)
                )
                .center_x(iced::Length::Fill),
            )
        )
        .height(iced::Length::Fill);
        tabbed_browser = tabbed_browser.push(document_viewer);
        return iced::widget::Container::new(tabbed_browser)
        .width(iced::Length::Fill)
        .height(iced::Length::Fill)
        .into();
    }
}

pub fn main() -> iced::Result {
    return iced::run(App::update, App::view);
}

Cargo.toml

[package]
name = "tiny-skia-bug"

[dependencies]
iced = { git = "https://github.com/DKolter/iced/", branch = "issue_2740", features=["advanced"] }

lufte avatar May 25 '25 18:05 lufte

Good catch, thank you @lufte! The issue was not applying the transformation to the clip bounds as well. The scrollable widget modified this transformation, which caused problems with the text clipping.

Shorter snippet to reproduce:

use iced::{
    Element, Length,
    widget::{column, scrollable, text},
};

#[derive(Default)]
pub struct App {}

#[derive(Debug)]
pub enum Message {}

impl App {
    fn update(&mut self, _message: ()) {}

    fn view(&self) -> Element<()> {
        scrollable(column((0..100).map(|i| text!("Item {i}").into())))
            .width(Length::Fill)
            .height(Length::Fill)
            .into()
    }
}

pub fn main() -> iced::Result {
    return iced::run(App::update, App::view);
}

DKolter avatar May 26 '25 09:05 DKolter

Glad to be of help @DKolter. There is another bug, related with scrolling and tiny-skia but not with this patch here, for which you might be of help. If you're interested I'll try to come up with a minimal example for that too and get in touch with you.

lufte avatar May 26 '25 19:05 lufte

Also fixes #2371

DKolter avatar May 30 '25 07:05 DKolter

This works great for me. I tested with different scaling (dpi) options on windows and it always looks good. Thanks very much for working on this @DKolter 🙂

DBJim avatar Jun 02 '25 08:06 DBJim