svg_preview: Update preview on every buffer edit (#41270)
Closes https://github.com/zed-industries/zed/issues/39104 This fixes an issue where the preview would not work for remote buffers in the process. Release Notes: - Fixed an issue where the SVG preview would not work in remote scenarios. - The SVG preview will now rerender on every keypress instead of only on saves.
This commit is contained in:
Generated
+1
-1
@@ -16495,7 +16495,7 @@ dependencies = [
|
||||
"editor",
|
||||
"file_icons",
|
||||
"gpui",
|
||||
"multi_buffer",
|
||||
"language",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
@@ -2,14 +2,13 @@ use crate::{
|
||||
AnyElement, AnyImageCache, App, Asset, AssetLogger, Bounds, DefiniteLength, Element, ElementId,
|
||||
Entity, GlobalElementId, Hitbox, Image, ImageCache, InspectorElementId, InteractiveElement,
|
||||
Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource,
|
||||
SMOOTH_SVG_SCALE_FACTOR, SharedString, SharedUri, StyleRefinement, Styled, SvgSize, Task,
|
||||
Window, px, swap_rgba_pa_to_bgra,
|
||||
SharedString, SharedUri, StyleRefinement, Styled, Task, Window, px,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use futures::{AsyncReadExt, Future};
|
||||
use image::{
|
||||
AnimationDecoder, DynamicImage, Frame, ImageBuffer, ImageError, ImageFormat, Rgba,
|
||||
AnimationDecoder, DynamicImage, Frame, ImageError, ImageFormat, Rgba,
|
||||
codecs::{gif::GifDecoder, webp::WebPDecoder},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
@@ -160,13 +159,15 @@ pub trait StyledImage: Sized {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the object fit for the image.
|
||||
/// Set a fallback function that will be invoked to render an error view should
|
||||
/// the image fail to load.
|
||||
fn with_fallback(mut self, fallback: impl Fn() -> AnyElement + 'static) -> Self {
|
||||
self.image_style().fallback = Some(Box::new(fallback));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the object fit for the image.
|
||||
/// Set a fallback function that will be invoked to render a view while the image
|
||||
/// is still being loaded.
|
||||
fn with_loading(mut self, loading: impl Fn() -> AnyElement + 'static) -> Self {
|
||||
self.image_style().loading = Some(Box::new(loading));
|
||||
self
|
||||
@@ -631,7 +632,7 @@ impl Asset for ImageAssetLoader {
|
||||
}
|
||||
};
|
||||
|
||||
let data = if let Ok(format) = image::guess_format(&bytes) {
|
||||
if let Ok(format) = image::guess_format(&bytes) {
|
||||
let data = match format {
|
||||
ImageFormat::Gif => {
|
||||
let decoder = GifDecoder::new(Cursor::new(&bytes))?;
|
||||
@@ -689,25 +690,12 @@ impl Asset for ImageAssetLoader {
|
||||
}
|
||||
};
|
||||
|
||||
RenderImage::new(data)
|
||||
Ok(Arc::new(RenderImage::new(data)))
|
||||
} else {
|
||||
let pixmap =
|
||||
// TODO: Can we make svgs always rescale?
|
||||
svg_renderer.render_pixmap(&bytes, SvgSize::ScaleFactor(SMOOTH_SVG_SCALE_FACTOR))?;
|
||||
|
||||
let mut buffer =
|
||||
ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take()).unwrap();
|
||||
|
||||
for pixel in buffer.chunks_exact_mut(4) {
|
||||
swap_rgba_pa_to_bgra(pixel);
|
||||
}
|
||||
|
||||
let mut image = RenderImage::new(SmallVec::from_elem(Frame::new(buffer), 1));
|
||||
image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
|
||||
image
|
||||
};
|
||||
|
||||
Ok(Arc::new(data))
|
||||
svg_renderer
|
||||
.render_single_frame(&bytes, 1.0, true)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ pub use smol::Timer;
|
||||
pub use style::*;
|
||||
pub use styled::*;
|
||||
pub use subscription::*;
|
||||
use svg_renderer::*;
|
||||
pub use svg_renderer::*;
|
||||
pub(crate) use tab_stop::*;
|
||||
pub use taffy::{AvailableSpace, LayoutId};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -40,7 +40,7 @@ use crate::{
|
||||
DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
|
||||
ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
|
||||
Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph,
|
||||
ShapedRun, SharedString, Size, SvgRenderer, SvgSize, SystemWindowTab, Task, TaskLabel, Window,
|
||||
ShapedRun, SharedString, Size, SvgRenderer, SystemWindowTab, Task, TaskLabel, Window,
|
||||
WindowControlArea, hash, point, px, size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -1825,13 +1825,9 @@ impl Image {
|
||||
ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
|
||||
ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
|
||||
ImageFormat::Svg => {
|
||||
let pixmap = svg_renderer.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
|
||||
|
||||
let buffer =
|
||||
image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
|
||||
.unwrap();
|
||||
|
||||
SmallVec::from_elem(Frame::new(buffer), 1)
|
||||
return svg_renderer
|
||||
.render_single_frame(&self.bytes, 1.0, false)
|
||||
.map_err(Into::into);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
|
||||
use crate::{
|
||||
AssetSource, DevicePixels, IsZero, RenderImage, Result, SharedString, Size,
|
||||
swap_rgba_pa_to_bgra,
|
||||
};
|
||||
use image::Frame;
|
||||
use resvg::tiny_skia::Pixmap;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
hash::Hash,
|
||||
sync::{Arc, LazyLock},
|
||||
@@ -15,17 +20,22 @@ pub(crate) struct RenderSvgParams {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// A struct holding everything necessary to render SVGs.
|
||||
pub struct SvgRenderer {
|
||||
asset_source: Arc<dyn AssetSource>,
|
||||
usvg_options: Arc<usvg::Options<'static>>,
|
||||
}
|
||||
|
||||
/// The size in which to render the SVG.
|
||||
pub enum SvgSize {
|
||||
/// An absolute size in device pixels.
|
||||
Size(Size<DevicePixels>),
|
||||
/// A scaling factor to apply to the size provided by the SVG.
|
||||
ScaleFactor(f32),
|
||||
}
|
||||
|
||||
impl SvgRenderer {
|
||||
/// Creates a new SVG renderer with the provided asset source.
|
||||
pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
|
||||
static FONT_DB: LazyLock<Arc<usvg::fontdb::Database>> = LazyLock::new(|| {
|
||||
let mut db = usvg::fontdb::Database::new();
|
||||
@@ -54,7 +64,35 @@ impl SvgRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render(
|
||||
/// Renders the given bytes into an image buffer.
|
||||
pub fn render_single_frame(
|
||||
&self,
|
||||
bytes: &[u8],
|
||||
scale_factor: f32,
|
||||
to_brga: bool,
|
||||
) -> Result<Arc<RenderImage>, usvg::Error> {
|
||||
self.render_pixmap(
|
||||
bytes,
|
||||
SvgSize::ScaleFactor(scale_factor * SMOOTH_SVG_SCALE_FACTOR),
|
||||
)
|
||||
.map(|pixmap| {
|
||||
let mut buffer =
|
||||
image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
|
||||
.unwrap();
|
||||
|
||||
if to_brga {
|
||||
for pixel in buffer.chunks_exact_mut(4) {
|
||||
swap_rgba_pa_to_bgra(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
let mut image = RenderImage::new(SmallVec::from_const([Frame::new(buffer)]));
|
||||
image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
|
||||
Arc::new(image)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn render_alpha_mask(
|
||||
&self,
|
||||
params: &RenderSvgParams,
|
||||
) -> Result<Option<(Size<DevicePixels>, Vec<u8>)>> {
|
||||
@@ -80,7 +118,7 @@ impl SvgRenderer {
|
||||
Ok(Some((size, alpha_mask)))
|
||||
}
|
||||
|
||||
pub fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
|
||||
fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
|
||||
let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
|
||||
let svg_size = tree.size();
|
||||
let scale = match size {
|
||||
|
||||
@@ -3104,7 +3104,7 @@ impl Window {
|
||||
let Some(tile) =
|
||||
self.sprite_atlas
|
||||
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||
let Some((size, bytes)) = cx.svg_renderer.render(¶ms)? else {
|
||||
let Some((size, bytes)) = cx.svg_renderer.render_alpha_mask(¶ms)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some((size, Cow::Owned(bytes))))
|
||||
|
||||
@@ -15,6 +15,6 @@ path = "src/svg_preview.rs"
|
||||
editor.workspace = true
|
||||
file_icons.workspace = true
|
||||
gpui.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
language.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::path::PathBuf;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
use editor::Editor;
|
||||
use file_icons::FileIcons;
|
||||
use gpui::{
|
||||
App, Context, Entity, EventEmitter, FocusHandle, Focusable, ImageSource, IntoElement,
|
||||
ParentElement, Render, Resource, RetainAllImageCache, Styled, Subscription, WeakEntity, Window,
|
||||
div, img,
|
||||
App, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render,
|
||||
RenderImage, Styled, Subscription, Task, WeakEntity, Window, div, img,
|
||||
};
|
||||
use multi_buffer::{Event as MultiBufferEvent, MultiBuffer};
|
||||
use language::{Buffer, BufferEvent};
|
||||
use ui::prelude::*;
|
||||
use workspace::item::Item;
|
||||
use workspace::{Pane, Workspace};
|
||||
@@ -16,9 +16,10 @@ use crate::{OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide};
|
||||
|
||||
pub struct SvgPreviewView {
|
||||
focus_handle: FocusHandle,
|
||||
svg_path: Option<PathBuf>,
|
||||
image_cache: Entity<RetainAllImageCache>,
|
||||
_buffer_subscription: Subscription,
|
||||
buffer: Option<Entity<Buffer>>,
|
||||
current_svg: Option<Result<Arc<RenderImage>, SharedString>>,
|
||||
_refresh: Task<()>,
|
||||
_buffer_subscription: Option<Subscription>,
|
||||
_workspace_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
@@ -31,6 +32,182 @@ pub enum SvgPreviewMode {
|
||||
}
|
||||
|
||||
impl SvgPreviewView {
|
||||
pub fn new(
|
||||
mode: SvgPreviewMode,
|
||||
active_editor: Entity<Editor>,
|
||||
workspace_handle: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let workspace_subscription = if mode == SvgPreviewMode::Follow
|
||||
&& let Some(workspace) = workspace_handle.upgrade()
|
||||
{
|
||||
Some(Self::subscribe_to_workspace(workspace, window, cx))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let buffer = active_editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.clone()
|
||||
.read_with(cx, |buffer, _cx| buffer.as_singleton());
|
||||
|
||||
let subscription = buffer
|
||||
.as_ref()
|
||||
.map(|buffer| Self::create_buffer_subscription(buffer, window, cx));
|
||||
|
||||
let mut this = Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
buffer,
|
||||
current_svg: None,
|
||||
_buffer_subscription: subscription,
|
||||
_workspace_subscription: workspace_subscription,
|
||||
_refresh: Task::ready(()),
|
||||
};
|
||||
this.render_image(window, cx);
|
||||
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
fn subscribe_to_workspace(
|
||||
workspace: Entity<Workspace>,
|
||||
window: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Subscription {
|
||||
cx.subscribe_in(
|
||||
&workspace,
|
||||
window,
|
||||
move |this: &mut SvgPreviewView, workspace, event: &workspace::Event, window, cx| {
|
||||
if let workspace::Event::ActiveItemChanged = event {
|
||||
let workspace = workspace.read(cx);
|
||||
if let Some(active_item) = workspace.active_item(cx)
|
||||
&& let Some(editor) = active_item.downcast::<Editor>()
|
||||
&& Self::is_svg_file(&editor, cx)
|
||||
{
|
||||
let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() else {
|
||||
return;
|
||||
};
|
||||
if this.buffer.as_ref() != Some(&buffer) {
|
||||
this._buffer_subscription =
|
||||
Some(Self::create_buffer_subscription(&buffer, window, cx));
|
||||
this.buffer = Some(buffer);
|
||||
this.render_image(window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
} else {
|
||||
this.set_current(None, window, cx);
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn render_image(&mut self, window: &Window, cx: &mut Context<Self>) {
|
||||
let Some(buffer) = self.buffer.as_ref() else {
|
||||
return;
|
||||
};
|
||||
const SCALE_FACTOR: f32 = 1.0;
|
||||
|
||||
let renderer = cx.svg_renderer();
|
||||
let content = buffer.read(cx).snapshot();
|
||||
let background_task = cx.background_spawn(async move {
|
||||
renderer.render_single_frame(content.text().as_bytes(), SCALE_FACTOR, true)
|
||||
});
|
||||
|
||||
self._refresh = cx.spawn_in(window, async move |this, cx| {
|
||||
let result = background_task.await;
|
||||
|
||||
this.update_in(cx, |view, window, cx| {
|
||||
let current = result.map_err(|e| e.to_string().into());
|
||||
view.set_current(Some(current), window, cx);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
fn set_current(
|
||||
&mut self,
|
||||
image: Option<Result<Arc<RenderImage>, SharedString>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(Ok(image)) = mem::replace(&mut self.current_svg, image) {
|
||||
window.drop_image(image).ok();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn find_existing_preview_item_idx(
|
||||
pane: &Pane,
|
||||
editor: &Entity<Editor>,
|
||||
cx: &App,
|
||||
) -> Option<usize> {
|
||||
let buffer_id = editor.read(cx).buffer().entity_id();
|
||||
pane.items_of_type::<SvgPreviewView>()
|
||||
.find(|view| {
|
||||
view.read(cx)
|
||||
.buffer
|
||||
.as_ref()
|
||||
.is_some_and(|buffer| buffer.entity_id() == buffer_id)
|
||||
})
|
||||
.and_then(|view| pane.index_for_item(&view))
|
||||
}
|
||||
|
||||
pub fn resolve_active_item_as_svg_editor(
|
||||
workspace: &Workspace,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<Editor>> {
|
||||
workspace
|
||||
.active_item(cx)?
|
||||
.act_as::<Editor>(cx)
|
||||
.filter(|editor| Self::is_svg_file(&editor, cx))
|
||||
}
|
||||
|
||||
fn create_svg_view(
|
||||
mode: SvgPreviewMode,
|
||||
workspace: &mut Workspace,
|
||||
editor: Entity<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<SvgPreviewView> {
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
SvgPreviewView::new(mode, editor, workspace_handle, window, cx)
|
||||
}
|
||||
|
||||
fn create_buffer_subscription(
|
||||
buffer: &Entity<Buffer>,
|
||||
window: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Subscription {
|
||||
cx.subscribe_in(
|
||||
buffer,
|
||||
window,
|
||||
move |this, _buffer, event: &BufferEvent, window, cx| match event {
|
||||
BufferEvent::Edited | BufferEvent::Saved => {
|
||||
this.render_image(window, cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_svg_file(editor: &Entity<Editor>, cx: &App) -> bool {
|
||||
editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.is_some_and(|file| {
|
||||
file.path()
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register(workspace: &mut Workspace, _window: &mut Window, _cx: &mut Context<Workspace>) {
|
||||
workspace.register_action(move |workspace, _: &OpenPreview, window, cx| {
|
||||
if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx)
|
||||
@@ -104,154 +281,6 @@ impl SvgPreviewView {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn find_existing_preview_item_idx(
|
||||
pane: &Pane,
|
||||
editor: &Entity<Editor>,
|
||||
cx: &App,
|
||||
) -> Option<usize> {
|
||||
let editor_path = Self::get_svg_path(editor.read(cx).buffer(), cx);
|
||||
pane.items_of_type::<SvgPreviewView>()
|
||||
.find(|view| {
|
||||
let view_read = view.read(cx);
|
||||
view_read.svg_path.is_some() && view_read.svg_path == editor_path
|
||||
})
|
||||
.and_then(|view| pane.index_for_item(&view))
|
||||
}
|
||||
|
||||
pub fn resolve_active_item_as_svg_editor(
|
||||
workspace: &Workspace,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<Editor>> {
|
||||
let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
|
||||
|
||||
if Self::is_svg_file(&editor, cx) {
|
||||
Some(editor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn create_svg_view(
|
||||
mode: SvgPreviewMode,
|
||||
workspace: &mut Workspace,
|
||||
editor: Entity<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<SvgPreviewView> {
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
SvgPreviewView::new(mode, editor, workspace_handle, window, cx)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
mode: SvgPreviewMode,
|
||||
active_editor: Entity<Editor>,
|
||||
workspace_handle: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let image_cache = RetainAllImageCache::new(cx);
|
||||
let buffer = active_editor.read(cx).buffer();
|
||||
let svg_path = Self::get_svg_path(buffer, cx);
|
||||
let subscription = Self::create_buffer_subscription(&buffer.clone(), window, cx);
|
||||
|
||||
// Subscribe to workspace active item changes to follow SVG files
|
||||
let workspace_subscription = if mode == SvgPreviewMode::Follow {
|
||||
workspace_handle.upgrade().map(|workspace_handle| {
|
||||
cx.subscribe_in(
|
||||
&workspace_handle,
|
||||
window,
|
||||
|this: &mut SvgPreviewView,
|
||||
workspace,
|
||||
event: &workspace::Event,
|
||||
window,
|
||||
cx| {
|
||||
if let workspace::Event::ActiveItemChanged = event {
|
||||
let workspace_read = workspace.read(cx);
|
||||
if let Some(active_item) = workspace_read.active_item(cx)
|
||||
&& let Some(editor) = active_item.downcast::<Editor>()
|
||||
&& Self::is_svg_file(&editor, cx)
|
||||
{
|
||||
let buffer = editor.read(cx).buffer();
|
||||
let new_path = Self::get_svg_path(&buffer, cx);
|
||||
if this.svg_path != new_path {
|
||||
this.svg_path = new_path;
|
||||
this._buffer_subscription =
|
||||
Self::create_buffer_subscription(
|
||||
&buffer.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
svg_path,
|
||||
image_cache,
|
||||
_buffer_subscription: subscription,
|
||||
_workspace_subscription: workspace_subscription,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn create_buffer_subscription(
|
||||
active_buffer: &Entity<MultiBuffer>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Subscription {
|
||||
cx.subscribe_in(
|
||||
active_buffer,
|
||||
window,
|
||||
|this: &mut SvgPreviewView, buffer, event: &MultiBufferEvent, window, cx| {
|
||||
let potential_path_change = event == &MultiBufferEvent::FileHandleChanged;
|
||||
if event == &MultiBufferEvent::Saved || potential_path_change {
|
||||
// Remove cached image to force reload
|
||||
if let Some(svg_path) = &this.svg_path {
|
||||
let resource = Resource::Path(svg_path.clone().into());
|
||||
this.image_cache.update(cx, |cache, cx| {
|
||||
cache.remove(&resource, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
if potential_path_change {
|
||||
this.svg_path = Self::get_svg_path(buffer, cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_svg_file(editor: &Entity<Editor>, cx: &App) -> bool {
|
||||
let buffer = editor.read(cx).buffer().read(cx);
|
||||
if let Some(buffer) = buffer.as_singleton()
|
||||
&& let Some(file) = buffer.read(cx).file()
|
||||
{
|
||||
return file
|
||||
.path()
|
||||
.extension()
|
||||
.map(|ext| ext.eq_ignore_ascii_case("svg"))
|
||||
.unwrap_or(false);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_svg_path(buffer: &Entity<MultiBuffer>, cx: &App) -> Option<PathBuf> {
|
||||
let buffer = buffer.read(cx).as_singleton()?;
|
||||
let file = buffer.read(cx).file()?;
|
||||
let local_file = file.as_local()?;
|
||||
Some(local_file.abs_path(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for SvgPreviewView {
|
||||
@@ -265,20 +294,19 @@ impl Render for SvgPreviewView {
|
||||
.flex()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.child(if let Some(svg_path) = &self.svg_path {
|
||||
img(ImageSource::from(svg_path.clone()))
|
||||
.image_cache(&self.image_cache)
|
||||
.max_w_full()
|
||||
.max_h_full()
|
||||
.with_fallback(|| {
|
||||
div()
|
||||
.map(|this| match self.current_svg.clone() {
|
||||
Some(Ok(image)) => {
|
||||
this.child(img(image).max_w_full().max_h_full().with_fallback(|| {
|
||||
h_flex()
|
||||
.p_4()
|
||||
.child("Failed to load SVG file")
|
||||
.gap_2()
|
||||
.child(Icon::new(IconName::Warning))
|
||||
.child("Failed to load SVG image")
|
||||
.into_any_element()
|
||||
})
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().p_4().child("No SVG file selected").into_any_element()
|
||||
}))
|
||||
}
|
||||
Some(Err(e)) => this.child(div().p_4().child(e).into_any_element()),
|
||||
None => this.child(div().p_4().child("No SVG file selected")),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -295,20 +323,19 @@ impl Item for SvgPreviewView {
|
||||
type Event = ();
|
||||
|
||||
fn tab_icon(&self, _window: &Window, cx: &App) -> Option<Icon> {
|
||||
// Use the same icon as SVG files in the file tree
|
||||
self.svg_path
|
||||
self.buffer
|
||||
.as_ref()
|
||||
.and_then(|svg_path| FileIcons::get_icon(svg_path, cx))
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.and_then(|file| FileIcons::get_icon(file.path().as_std_path(), cx))
|
||||
.map(Icon::from_path)
|
||||
.or_else(|| Some(Icon::new(IconName::Image)))
|
||||
}
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||
self.svg_path
|
||||
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
|
||||
self.buffer
|
||||
.as_ref()
|
||||
.and_then(|svg_path| svg_path.file_name())
|
||||
.map(|name| name.to_string_lossy())
|
||||
.map(|name| format!("Preview {}", name).into())
|
||||
.and_then(|svg_path| svg_path.read(cx).file())
|
||||
.map(|name| format!("Preview {}", name.file_name(cx)).into())
|
||||
.unwrap_or_else(|| "SVG Preview".into())
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[
|
||||
("blade_graphics", log::LevelFilter::Warn),
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
|
||||
("naga::back::spv::writer", log::LevelFilter::Warn),
|
||||
// usvg prints a lot of warnings on rendering an SVG with partial errors, which
|
||||
// can happen a lot with the SVG preview
|
||||
("usvg::parser::style", log::LevelFilter::Error),
|
||||
];
|
||||
|
||||
pub fn init_env_filter(filter: env_config::EnvFilter) {
|
||||
|
||||
Reference in New Issue
Block a user