chore: clean up

This commit is contained in:
2024-12-20 10:53:45 +07:00
parent 6ba55b801c
commit 1ffaefd729
5 changed files with 7 additions and 835 deletions

View File

@@ -22,5 +22,3 @@ unicode-segmentation = "1.11.0"
uuid = "1.10"
once_cell = "1.19.0"
image = "0.25.1"
usvg = { version = "0.44.0", default-features = false, features = ["system-fonts", "text"] }
resvg = { version = "0.44.0", default-features = false, features = ["system-fonts", "text"] }

View File

@@ -4,7 +4,6 @@ mod focusable;
mod icon;
mod root;
mod styled;
mod svg_img;
mod title_bar;
pub mod accordion;
@@ -44,7 +43,6 @@ pub mod switch;
pub mod tab;
pub mod theme;
pub mod tooltip;
pub mod virtual_list;
pub use crate::Disableable;
pub use event::InteractiveElementExt;
@@ -52,11 +50,9 @@ pub use focusable::FocusableCycle;
pub use root::{ContextModal, Root};
pub use styled::*;
pub use title_bar::*;
pub use virtual_list::{h_virtual_list, v_virtual_list, VirtualList};
pub use colors::*;
pub use icon::*;
pub use svg_img::*;
use rust_embed::RustEmbed;

View File

@@ -1,301 +0,0 @@
use std::{
hash::Hash,
ops::Deref,
sync::{Arc, LazyLock},
};
use gpui::{
px, size, AppContext, Asset, Bounds, Element, Hitbox, ImageCacheError, InteractiveElement,
Interactivity, IntoElement, IsZero, Pixels, RenderImage, SharedString, Size, StyleRefinement,
Styled, WindowContext,
};
use image::Frame;
use smallvec::SmallVec;
use image::ImageBuffer;
use crate::Assets;
const SCALE: f32 = 2.;
const FONT_PATH: &str = "fonts/NotoSans-Regular.ttf";
static OPTIONS: LazyLock<usvg::Options> = LazyLock::new(|| {
let mut options = usvg::Options::default();
if let Some(font_data) = Assets::get(FONT_PATH).map(|f| f.data) {
options.fontdb_mut().load_font_data(font_data.into());
}
options
});
#[derive(Debug, Clone, Hash)]
pub enum SvgSource {
/// A svg bytes
Data(Arc<[u8]>),
/// An asset path
Path(SharedString),
}
impl From<&[u8]> for SvgSource {
fn from(data: &[u8]) -> Self {
Self::Data(data.into())
}
}
impl From<Arc<[u8]>> for SvgSource {
fn from(data: Arc<[u8]>) -> Self {
Self::Data(data)
}
}
impl From<SharedString> for SvgSource {
fn from(path: SharedString) -> Self {
Self::Path(path)
}
}
impl From<&'static str> for SvgSource {
fn from(path: &'static str) -> Self {
Self::Path(path.into())
}
}
impl Clone for SvgImg {
fn clone(&self) -> Self {
Self {
interactivity: Interactivity::default(),
source: self.source.clone(),
size: self.size,
}
}
}
enum Image {}
#[derive(Debug, Clone)]
struct ImageSource {
source: SvgSource,
size: Size<Pixels>,
}
impl Hash for ImageSource {
/// Hash to to control the Asset cache
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.source.hash(state);
}
}
impl Asset for Image {
type Source = ImageSource;
type Output = Result<Arc<RenderImage>, ImageCacheError>;
fn load(
source: Self::Source,
cx: &mut AppContext,
) -> impl std::future::Future<Output = Self::Output> + Send + 'static {
let asset_source = cx.asset_source().clone();
async move {
let size = source.size;
if size.width.is_zero() || size.height.is_zero() {
return Err(usvg::Error::InvalidSize.into());
}
let size = Size {
width: (size.width * SCALE).ceil(),
height: (size.height * SCALE).ceil(),
};
let bytes = match source.source {
SvgSource::Data(data) => data,
SvgSource::Path(path) => {
if let Ok(Some(data)) = asset_source.load(&path) {
data.deref().to_vec().into()
} else {
Err(std::io::Error::other(format!(
"failed to load svg image from path: {}",
path
)))
.map_err(|e| ImageCacheError::Io(Arc::new(e)))?
}
}
};
let tree = usvg::Tree::from_data(&bytes, &OPTIONS)?;
let mut pixmap =
resvg::tiny_skia::Pixmap::new(size.width.0 as u32, size.height.0 as u32)
.ok_or(usvg::Error::InvalidSize)?;
let transform = resvg::tiny_skia::Transform::from_scale(SCALE, SCALE);
resvg::render(&tree, transform, &mut pixmap.as_mut());
let mut buffer = ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
.expect("invalid svg image buffer");
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
for pixel in buffer.chunks_exact_mut(4) {
pixel.swap(0, 2);
if pixel[3] > 0 {
let a = pixel[3] as f32 / 255.;
pixel[0] = (pixel[0] as f32 / a) as u8;
pixel[1] = (pixel[1] as f32 / a) as u8;
pixel[2] = (pixel[2] as f32 / a) as u8;
}
}
Ok(Arc::new(RenderImage::new(SmallVec::from_elem(
Frame::new(buffer),
1,
))))
}
}
}
pub struct SvgImg {
interactivity: Interactivity,
source: Option<SvgSource>,
size: Size<Pixels>,
}
impl SvgImg {
/// Create a new svg image element.
///
/// The `src_width` and `src_height` are the original width and height of the svg image.
pub fn new() -> Self {
Self {
interactivity: Interactivity::default(),
source: None,
size: Size::default(),
}
}
/// Set the path of the svg image from the asset.
///
/// The `size` argument is the size of the original svg image.
#[must_use]
pub fn source(
mut self,
source: impl Into<SvgSource>,
width: impl Into<Pixels>,
height: impl Into<Pixels>,
) -> Self {
self.source = Some(source.into());
self.size = size(width.into(), height.into());
self
}
}
impl Default for SvgImg {
fn default() -> Self {
Self::new()
}
}
impl IntoElement for SvgImg {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for SvgImg {
type RequestLayoutState = ();
type PrepaintState = Option<Hitbox>;
fn id(&self) -> Option<gpui::ElementId> {
self.interactivity.element_id.clone()
}
fn request_layout(
&mut self,
global_id: Option<&gpui::GlobalElementId>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
let layout_id = self
.interactivity
.request_layout(global_id, cx, |style, cx| cx.request_layout(style, None));
(layout_id, ())
}
fn prepaint(
&mut self,
global_id: Option<&gpui::GlobalElementId>,
bounds: gpui::Bounds<gpui::Pixels>,
_: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
self.interactivity
.prepaint(global_id, bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
}
fn paint(
&mut self,
global_id: Option<&gpui::GlobalElementId>,
bounds: gpui::Bounds<gpui::Pixels>,
_: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
let source = self.source.clone();
self.interactivity
.paint(global_id, bounds, hitbox.as_ref(), cx, |_style, cx| {
let size = self.size;
let data = if let Some(source) = source {
match cx.use_asset::<Image>(&ImageSource { source, size }) {
Some(Ok(data)) => Some(data),
_ => None,
}
} else {
None
};
if let Some(data) = data {
// To calculate the ratio of the original image size to the container bounds size.
// Scale by shortest side (width or height) to get a fit image.
// And center the image in the container bounds.
let ratio = if bounds.size.width < bounds.size.height {
bounds.size.width / size.width
} else {
bounds.size.height / size.height
};
let ratio = ratio.min(1.0);
let new_size = gpui::Size {
width: size.width * ratio,
height: size.height * ratio,
};
let new_origin = gpui::Point {
x: bounds.origin.x + px(((bounds.size.width - new_size.width) / 2.).into()),
y: bounds.origin.y
+ px(((bounds.size.height - new_size.height) / 2.).into()),
};
let img_bounds = Bounds {
origin: new_origin.map(|origin| origin.floor()),
size: new_size.map(|size| size.ceil()),
};
match cx.paint_image(img_bounds, px(0.).into(), data, 0, false) {
Ok(_) => {}
Err(err) => eprintln!("failed to paint svg image: {:?}", err),
}
}
})
}
}
impl Styled for SvgImg {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style
}
}
impl InteractiveElement for SvgImg {
fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity
}
}

View File

@@ -1,447 +0,0 @@
//! Vistual List for render a large number of differently sized rows/columns.
//!
//! > NOTE: This must ensure each column width or row height.
//!
//! Only visible range are rendered for performance reasons.
//!
//! Inspired by `gpui::uniform_list`.
//! https://github.com/zed-industries/zed/blob/0ae1603610ab6b265bdfbee7b8dbc23c5ab06edc/crates/gpui/src/elements/uniform_list.rs
//!
//! Unlike the `uniform_list`, the each item can have different size.
//!
//! This is useful for more complex layout, for example, a table with different row height.
use std::{cmp, ops::Range, rc::Rc};
use gpui::{
div, point, px, size, AnyElement, AvailableSpace, Axis, Bounds, ContentMask, Div, Element,
ElementId, GlobalElementId, Hitbox, InteractiveElement, IntoElement, IsZero as _, Pixels,
Render, ScrollHandle, Size, Stateful, StatefulInteractiveElement, StyleRefinement, Styled,
View, ViewContext, WindowContext,
};
use smallvec::SmallVec;
/// Create a virtual list in Vertical direction.
///
/// This is like `uniform_list` in GPUI, but support two axis.
///
/// The `item_sizes` is the size of each column.
pub fn v_virtual_list<R, V>(
view: View<V>,
id: impl Into<ElementId>,
item_sizes: Rc<Vec<Size<Pixels>>>,
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut ViewContext<V>) -> Vec<R>,
) -> VirtualList
where
R: IntoElement,
V: Render,
{
virtual_list(view, id, Axis::Vertical, item_sizes, f)
}
/// Create a virtual list in Horizontal direction.
pub fn h_virtual_list<R, V>(
view: View<V>,
id: impl Into<ElementId>,
item_sizes: Rc<Vec<Size<Pixels>>>,
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut ViewContext<V>) -> Vec<R>,
) -> VirtualList
where
R: IntoElement,
V: Render,
{
virtual_list(view, id, Axis::Horizontal, item_sizes, f)
}
pub(crate) fn virtual_list<R, V>(
view: View<V>,
id: impl Into<ElementId>,
axis: Axis,
item_sizes: Rc<Vec<Size<Pixels>>>,
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut ViewContext<V>) -> Vec<R>,
) -> VirtualList
where
R: IntoElement,
V: Render,
{
let id: ElementId = id.into();
let scroll_handle = ScrollHandle::default();
let render_range = move |visible_range, content_size, cx: &mut WindowContext| {
view.update(cx, |this, cx| {
f(this, visible_range, content_size, cx)
.into_iter()
.map(|component| component.into_any_element())
.collect()
})
};
VirtualList {
id: id.clone(),
axis,
base: div()
.id(id)
.size_full()
.overflow_scroll()
.track_scroll(&scroll_handle),
scroll_handle,
items_count: item_sizes.len(),
item_sizes,
render_items: Box::new(render_range),
}
}
type RenderItems = Box<
dyn for<'a> Fn(Range<usize>, Size<Pixels>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>,
>;
/// VirtualItem component for rendering a large number of differently sized columns.
pub struct VirtualList {
id: ElementId,
axis: Axis,
base: Stateful<Div>,
scroll_handle: ScrollHandle,
// scroll_handle: ScrollHandle,
items_count: usize,
item_sizes: Rc<Vec<Size<Pixels>>>,
render_items: RenderItems,
}
impl Styled for VirtualList {
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
}
}
impl VirtualList {
pub fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
self.base = self.base.track_scroll(scroll_handle);
self.scroll_handle = scroll_handle.clone();
self
}
/// Specify for table
#[allow(dead_code)]
pub(crate) fn with_scroll_handle(mut self, scroll_handle: &ScrollHandle) -> Self {
self.base = div().id(self.id.clone()).size_full();
self.scroll_handle = scroll_handle.clone();
self
}
/// Measure first item to get the size.
fn measure_item(&self, cx: &mut WindowContext) -> Size<Pixels> {
if self.items_count == 0 {
return Size::default();
}
let item_ix = 0;
let mut items = (self.render_items)(item_ix..item_ix + 1, Size::default(), cx);
let Some(mut item_to_measure) = items.pop() else {
return Size::default();
};
let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
item_to_measure.layout_as_root(available_space, cx)
}
}
/// Frame state used by the [VirtualItem].
pub struct VirtualListFrameState {
/// Visible items to be painted.
items: SmallVec<[AnyElement; 32]>,
item_sizes: Vec<Pixels>,
item_origins: Vec<Pixels>,
}
impl IntoElement for VirtualList {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for VirtualList {
type RequestLayoutState = VirtualListFrameState;
type PrepaintState = Option<Hitbox>;
fn id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
let style = self.base.interactivity().compute_style(global_id, None, cx);
let font_size = cx.text_style().font_size.to_pixels(cx.rem_size());
// Including the gap between items for calculate the item size
let gap = match self.axis {
Axis::Horizontal => style.gap.width,
Axis::Vertical => style.gap.height,
}
.to_pixels(font_size.into(), cx.rem_size());
// TODO: To cache the item_sizes, item_origins
// If there have 500,000 items, this method will speed about 500~600µs
// let start = std::time::Instant::now();
// Prepare each item's size by axis
let item_sizes = match self.axis {
Axis::Horizontal => self
.item_sizes
.iter()
.enumerate()
.map(|(i, size)| {
if i == self.items_count - 1 {
size.width
} else {
size.width + gap
}
})
.collect::<Vec<_>>(),
Axis::Vertical => self
.item_sizes
.iter()
.enumerate()
.map(|(i, size)| {
if i == self.items_count - 1 {
size.height
} else {
size.height + gap
}
})
.collect::<Vec<_>>(),
};
// Prepare each item's origin by axis
let item_origins = match self.axis {
Axis::Horizontal => item_sizes
.iter()
.scan(px(0.), |cumulative_x, size| {
let x = *cumulative_x;
*cumulative_x += *size;
Some(x)
})
.collect::<Vec<_>>(),
Axis::Vertical => item_sizes
.iter()
.scan(px(0.), |cumulative_y, size| {
let y = *cumulative_y;
*cumulative_y += *size;
Some(y)
})
.collect::<Vec<_>>(),
};
// println!("layout: {} {:?}", item_sizes.len(), start.elapsed());
let (layout_id, _) = self.base.request_layout(global_id, cx);
(
layout_id,
VirtualListFrameState {
items: SmallVec::new(),
item_sizes,
item_origins,
},
)
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
let style = self.base.interactivity().compute_style(global_id, None, cx);
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let first_item_size = self.measure_item(cx);
let padded_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top + padding.top),
bounds.bottom_right()
- point(border.right + padding.right, border.bottom + padding.bottom),
);
// Get border + padding pixel size
let padding_size = match self.axis {
Axis::Horizontal => border.left + padding.left + border.right + padding.right,
Axis::Vertical => border.top + padding.top + border.bottom + padding.bottom,
};
let item_sizes = &layout.item_sizes;
let item_origins = &layout.item_origins;
let content_size = match self.axis {
Axis::Horizontal => Size {
width: px(item_sizes.iter().map(|size| size.0).sum::<f32>()) + padding_size,
height: (first_item_size.height + padding_size).max(padded_bounds.size.height),
},
Axis::Vertical => Size {
width: (first_item_size.width + padding_size).max(padded_bounds.size.width),
height: px(item_sizes.iter().map(|size| size.0).sum::<f32>()) + padding_size,
},
};
self.base.interactivity().prepaint(
global_id,
bounds,
content_size,
cx,
|style, _, hitbox, cx| {
let mut scroll_offset = self.scroll_handle.offset();
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let padded_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top),
bounds.bottom_right() - point(border.right + padding.right, border.bottom),
);
if self.items_count > 0 {
let is_scrolled = match self.axis {
Axis::Horizontal => !scroll_offset.x.is_zero(),
Axis::Vertical => !scroll_offset.y.is_zero(),
};
let min_scroll_offset = match self.axis {
Axis::Horizontal => padded_bounds.size.width - content_size.width,
Axis::Vertical => padded_bounds.size.height - content_size.height,
};
if is_scrolled {
match self.axis {
Axis::Horizontal if scroll_offset.x < min_scroll_offset => {
scroll_offset.x = min_scroll_offset;
}
Axis::Vertical if scroll_offset.y < min_scroll_offset => {
scroll_offset.y = min_scroll_offset;
}
_ => {}
}
}
let (first_visible_element_ix, last_visible_element_ix) = match self.axis {
Axis::Horizontal => {
let mut cumulative_size = px(0.);
let mut first_visible_element_ix = 0;
for (i, &size) in item_sizes.iter().enumerate() {
cumulative_size += size;
if cumulative_size > -(scroll_offset.x + padding.left) {
first_visible_element_ix = i;
break;
}
}
cumulative_size = px(0.);
let mut last_visible_element_ix = 0;
for (i, &size) in item_sizes.iter().enumerate() {
cumulative_size += size;
if cumulative_size > (-scroll_offset.x + padded_bounds.size.width) {
last_visible_element_ix = i + 1;
break;
}
}
if last_visible_element_ix == 0 {
last_visible_element_ix = self.items_count;
} else {
last_visible_element_ix += 1;
}
(first_visible_element_ix, last_visible_element_ix)
}
Axis::Vertical => {
let mut cumulative_size = px(0.);
let mut first_visible_element_ix = 0;
for (i, &size) in item_sizes.iter().enumerate() {
cumulative_size += size;
if cumulative_size > -(scroll_offset.y + padding.top) {
first_visible_element_ix = i;
break;
}
}
cumulative_size = px(0.);
let mut last_visible_element_ix = 0;
for (i, &size) in item_sizes.iter().enumerate() {
cumulative_size += size;
if cumulative_size > (-scroll_offset.y + padded_bounds.size.height)
{
last_visible_element_ix = i + 1;
break;
}
}
if last_visible_element_ix == 0 {
last_visible_element_ix = self.items_count;
} else {
last_visible_element_ix += 1;
}
(first_visible_element_ix, last_visible_element_ix)
}
};
let visible_range = first_visible_element_ix
..cmp::min(last_visible_element_ix, self.items_count);
let items = (self.render_items)(visible_range.clone(), content_size, cx);
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (mut item, ix) in items.into_iter().zip(visible_range.clone()) {
let item_origin = match self.axis {
Axis::Horizontal => {
padded_bounds.origin
+ point(
item_origins[ix] + scroll_offset.x,
padding.top + scroll_offset.y,
)
}
Axis::Vertical => {
padded_bounds.origin
+ point(
scroll_offset.x,
padding.top + item_origins[ix] + scroll_offset.y,
)
}
};
let available_space = match self.axis {
Axis::Horizontal => size(
AvailableSpace::Definite(item_sizes[ix]),
AvailableSpace::Definite(padded_bounds.size.height),
),
Axis::Vertical => size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_sizes[ix]),
),
};
item.layout_as_root(available_space, cx);
item.prepaint_at(item_origin, cx);
layout.items.push(item);
}
});
}
hitbox
},
)
}
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
layout: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
self.base
.interactivity()
.paint(global_id, bounds, hitbox.as_ref(), cx, |_, cx| {
for item in &mut layout.items {
item.paint(cx);
}
})
}
}