update image cache
This commit is contained in:
@@ -1,186 +1,136 @@
|
|||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::mem::take;
|
use std::mem::take;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, Asset, AssetLogger, Entity, Global, ImageAssetLoader, ImageCache,
|
App, AppContext, Asset, AssetLogger, ElementId, Entity, ImageAssetLoader, ImageCache,
|
||||||
ImageCacheError, ImageCacheItem, ImageSource, RenderImage, Resource, Window, hash,
|
ImageCacheItem, ImageCacheProvider, ImageSource, Resource, hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Upper bound on the number of images the shared cache retains. Loading a
|
/// Default number of images each view's cache retains. Loading a new image
|
||||||
/// new image evicts the least recently used entry once this is reached.
|
/// evicts the least recently used entry once this is reached.
|
||||||
const MAX_IMAGES: usize = 128;
|
pub const MAX_IMAGES: usize = 128;
|
||||||
|
|
||||||
/// Global handle to the shared image cache, installed by [`init`].
|
pub fn image_cache(id: impl Into<ElementId>, max_items: usize) -> AppImageCacheProvider {
|
||||||
struct SharedImageCache(Entity<LruImageCache>);
|
AppImageCacheProvider {
|
||||||
|
id: id.into(),
|
||||||
impl Global for SharedImageCache {}
|
max_items,
|
||||||
|
}
|
||||||
/// Create the shared image cache and install it as a global. Call once at
|
|
||||||
/// startup (see `desktop/src/main.rs`), before any window opens.
|
|
||||||
pub fn init(cx: &mut App) -> Entity<LruImageCache> {
|
|
||||||
let cache = LruImageCache::new(MAX_IMAGES, cx);
|
|
||||||
cx.set_global(SharedImageCache(cache.clone()));
|
|
||||||
cache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shared image cache. Panics if [`init`] hasn't been called.
|
pub struct AppImageCacheProvider {
|
||||||
pub fn global(cx: &App) -> Entity<LruImageCache> {
|
id: ElementId,
|
||||||
cx.global::<SharedImageCache>().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drop every cached image, freeing the decoded image data, the GPU textures
|
|
||||||
/// and the raw fetched bytes. Images currently on screen are re-fetched on
|
|
||||||
/// the next frame.
|
|
||||||
pub fn clear(cx: &mut App, window: &mut Window) {
|
|
||||||
let cache = global(cx);
|
|
||||||
cache.update(cx, |cache, cx| cache.clear(window, cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear the shared cache when `view` is released — its last strong
|
|
||||||
/// reference is gone, e.g. the user closed the panel it was rendered in.
|
|
||||||
pub fn clear_on_release<T: 'static>(view: &Entity<T>, window: &Window, cx: &mut App) {
|
|
||||||
let cache = global(cx).downgrade();
|
|
||||||
cx.observe_release_in(view, window, move |_view, window, cx| {
|
|
||||||
if let Some(cache) = cache.upgrade() {
|
|
||||||
cache.update(cx, |cache, cx| cache.clear(window, cx));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A bounded LRU image cache.
|
|
||||||
///
|
|
||||||
/// Entries keep the [`Resource`] they were loaded from so that eviction,
|
|
||||||
/// clearing and release can also remove the asset from the asset system,
|
|
||||||
/// freeing the raw fetched bytes that would otherwise stay in memory.
|
|
||||||
pub struct LruImageCache {
|
|
||||||
max_items: usize,
|
max_items: usize,
|
||||||
/// Most recently used hashes first.
|
}
|
||||||
usage: VecDeque<u64>,
|
|
||||||
|
impl ImageCacheProvider for AppImageCacheProvider {
|
||||||
|
fn provide(&mut self, window: &mut gpui::Window, cx: &mut App) -> gpui::AnyImageCache {
|
||||||
|
window
|
||||||
|
.with_global_id(self.id.clone(), |id, window| {
|
||||||
|
window.with_element_state(id, |cache, _| {
|
||||||
|
let cache = cache.unwrap_or_else(|| AppImageCache::new(self.max_items, cx));
|
||||||
|
(cache.clone(), cache)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppImageCache {
|
||||||
|
max_items: usize,
|
||||||
|
usage_list: VecDeque<u64>,
|
||||||
cache: HashMap<u64, (ImageCacheItem, Resource)>,
|
cache: HashMap<u64, (ImageCacheItem, Resource)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LruImageCache {
|
impl AppImageCache {
|
||||||
/// Create a cache that holds at most `max_items` images. Cached images
|
|
||||||
/// are dropped from every window when the cache is released.
|
|
||||||
pub fn new(max_items: usize, cx: &mut App) -> Entity<Self> {
|
pub fn new(max_items: usize, cx: &mut App) -> Entity<Self> {
|
||||||
let max_items = max_items.max(1);
|
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
|
log::info!("Creating AppImageCacheProvider");
|
||||||
cx.on_release(|this: &mut Self, cx| {
|
cx.on_release(|this: &mut Self, cx| {
|
||||||
for (_, entry) in take(&mut this.cache) {
|
for (ix, (mut image, resource)) in take(&mut this.cache) {
|
||||||
unload(entry, None, cx);
|
if let Some(Ok(image)) = image.get() {
|
||||||
|
log::info!("Dropping image {ix}");
|
||||||
|
cx.drop_image(image, None);
|
||||||
|
}
|
||||||
|
ImageSource::Resource(resource).remove_asset(cx);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
Self {
|
|
||||||
|
AppImageCache {
|
||||||
max_items,
|
max_items,
|
||||||
usage: VecDeque::with_capacity(max_items),
|
usage_list: VecDeque::with_capacity(max_items),
|
||||||
cache: HashMap::with_capacity(max_items),
|
cache: HashMap::with_capacity(max_items),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop every cached image and remove every cached asset.
|
|
||||||
pub fn clear(&mut self, window: &mut Window, cx: &mut App) {
|
|
||||||
self.usage.clear();
|
|
||||||
for (_, entry) in take(&mut self.cache) {
|
|
||||||
unload(entry, Some(window), cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop a cache entry's decoded data and remove its resource from the asset
|
impl ImageCache for AppImageCache {
|
||||||
/// system, so both the decoded image and the raw fetched bytes are freed.
|
|
||||||
///
|
|
||||||
/// The atlas texture is freed **on the next frame, before it paints**, never
|
|
||||||
/// in the middle of one: the release that empties the cache can run at the
|
|
||||||
/// end of a frame's draw — after the scene was built, before it is presented
|
|
||||||
/// — and eviction runs while a frame is painting (`load` is called from
|
|
||||||
/// paint). The next frame also has to repaint every view instead of replaying
|
|
||||||
/// their recorded paint commands: `cached()` views reuse recorded commands
|
|
||||||
/// across frames, and those commands reference the atlas tiles being freed,
|
|
||||||
/// so a replay would hand the renderer a scene full of freed texture ids.
|
|
||||||
/// `window.refresh()` disables that reuse for exactly one frame.
|
|
||||||
///
|
|
||||||
/// `window` restricts the atlas removal to the current window; `None` removes
|
|
||||||
/// it from all windows (the cache entity is being released at shutdown, when
|
|
||||||
/// no scene is in flight).
|
|
||||||
fn unload(
|
|
||||||
(mut item, resource): (ImageCacheItem, Resource),
|
|
||||||
window: Option<&mut Window>,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
if let Some(Ok(image)) = item.get() {
|
|
||||||
match window {
|
|
||||||
Some(window) => {
|
|
||||||
window.on_next_frame(move |window, cx| {
|
|
||||||
window.refresh();
|
|
||||||
cx.drop_image(image, Some(window));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
None => cx.drop_image(image, None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImageSource::Resource(resource).remove_asset(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImageCache for LruImageCache {
|
|
||||||
fn load(
|
fn load(
|
||||||
&mut self,
|
&mut self,
|
||||||
resource: &Resource,
|
resource: &Resource,
|
||||||
window: &mut Window,
|
window: &mut gpui::Window,
|
||||||
cx: &mut App,
|
cx: &mut gpui::App,
|
||||||
) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
|
) -> Option<Result<std::sync::Arc<gpui::RenderImage>, gpui::ImageCacheError>> {
|
||||||
debug_assert_eq!(self.usage.len(), self.cache.len());
|
|
||||||
debug_assert!(self.cache.len() <= self.max_items);
|
|
||||||
|
|
||||||
let hash = hash(resource);
|
let hash = hash(resource);
|
||||||
|
|
||||||
if let Some((item, _)) = self.cache.get_mut(&hash) {
|
if let Some(item) = self.cache.get_mut(&hash) {
|
||||||
let current_ix = self
|
let current_idx = self
|
||||||
.usage
|
.usage_list
|
||||||
.iter()
|
.iter()
|
||||||
.position(|used| *used == hash)
|
.position(|item| *item == hash)
|
||||||
.expect("cache and usage list must stay in sync");
|
.expect("cache has an item usage_list doesn't");
|
||||||
self.usage.remove(current_ix);
|
|
||||||
self.usage.push_front(hash);
|
self.usage_list.remove(current_idx);
|
||||||
return item.get();
|
self.usage_list.push_front(hash);
|
||||||
|
|
||||||
|
return item.0.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
let fut = AssetLogger::<ImageAssetLoader>::load(resource.clone(), cx);
|
let load_future = AssetLogger::<ImageAssetLoader>::load(resource.clone(), cx);
|
||||||
let task = cx.background_executor().spawn(fut).shared();
|
let task = cx.background_executor().spawn(load_future).shared();
|
||||||
|
|
||||||
if self.usage.len() >= self.max_items {
|
if self.usage_list.len() >= self.max_items {
|
||||||
let oldest = self
|
log::info!("Image cache is full, evicting oldest item");
|
||||||
.usage
|
|
||||||
.pop_back()
|
if let Some(oldest) = self.usage_list.pop_back() {
|
||||||
.expect("usage list and cache must stay in sync");
|
let mut image = self
|
||||||
let entry = self
|
.cache
|
||||||
.cache
|
.remove(&oldest)
|
||||||
.remove(&oldest)
|
.expect("usage_list has an item cache doesn't");
|
||||||
.expect("usage list and cache must stay in sync");
|
|
||||||
unload(entry, Some(window), cx);
|
if let Some(Ok(image)) = image.0.get() {
|
||||||
|
log::info!("requesting image to be dropped");
|
||||||
|
cx.drop_image(image, Some(window));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageSource::Resource(image.1).remove_asset(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cache.insert(
|
self.cache.insert(
|
||||||
hash,
|
hash,
|
||||||
(ImageCacheItem::Loading(task.clone()), resource.clone()),
|
(
|
||||||
|
gpui::ImageCacheItem::Loading(task.clone()),
|
||||||
|
resource.clone(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
self.usage.push_front(hash);
|
self.usage_list.push_front(hash);
|
||||||
|
|
||||||
let entity = window.current_view();
|
let entity = window.current_view();
|
||||||
|
|
||||||
window
|
window
|
||||||
.spawn(cx, {
|
.spawn(cx, async move |cx| {
|
||||||
async move |cx| {
|
let result = task.await;
|
||||||
if let Err(error) = task.await {
|
|
||||||
log::error!("failed to load image into cache: {:?}", error);
|
if let Err(err) = result {
|
||||||
}
|
log::error!("error loading image into cache: {:?}", err);
|
||||||
cx.on_next_frame(move |_, cx| {
|
|
||||||
cx.notify(entity);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.on_next_frame(move |_, cx| {
|
||||||
|
cx.notify(entity);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use signed_state::{ProfileStore, RepoStore};
|
|||||||
use utils::relative_time;
|
use utils::relative_time;
|
||||||
|
|
||||||
use super::helpers::{placeholder, status_badge};
|
use super::helpers::{placeholder, status_badge};
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
|
|
||||||
/// Detail panel of a single issue.
|
/// Detail panel of a single issue.
|
||||||
pub struct IssueDetailView {
|
pub struct IssueDetailView {
|
||||||
@@ -35,9 +36,6 @@ impl IssueDetailView {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Issue author avatars stay in the shared cache until the panel closes.
|
|
||||||
crate::image_cache::clear_on_release(&cx.entity(), window, cx);
|
|
||||||
|
|
||||||
let comment_input =
|
let comment_input =
|
||||||
cx.new(|cx| TextareaState::new(window, cx).placeholder("Leave a comment…"));
|
cx.new(|cx| TextareaState::new(window, cx).placeholder("Leave a comment…"));
|
||||||
|
|
||||||
@@ -299,6 +297,7 @@ impl Render for IssueDetailView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.image_cache(image_cache("issue-detail", MAX_IMAGES))
|
||||||
.id("issue-detail")
|
.id("issue-detail")
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use utils::relative_time;
|
|||||||
|
|
||||||
use super::helpers::{placeholder, status_badge};
|
use super::helpers::{placeholder, status_badge};
|
||||||
use super::issue_detail::IssueDetailView;
|
use super::issue_detail::IssueDetailView;
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
|
|
||||||
/// Height of one issue row in the virtual list: 8px vertical padding
|
/// Height of one issue row in the virtual list: 8px vertical padding
|
||||||
/// (`py_2`) on top and bottom, a 32px title line (`h_8`) and a 24px meta
|
/// (`py_2`) on top and bottom, a 32px title line (`h_8`) and a 24px meta
|
||||||
@@ -88,13 +89,9 @@ impl IssuesView {
|
|||||||
dock_area: WeakEntity<DockArea>,
|
dock_area: WeakEntity<DockArea>,
|
||||||
store: Entity<RepoStore>,
|
store: Entity<RepoStore>,
|
||||||
repo_name: SharedString,
|
repo_name: SharedString,
|
||||||
window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Issue author avatars stay in the shared cache until the panel
|
|
||||||
// closes; free them then.
|
|
||||||
crate::image_cache::clear_on_release(&cx.entity(), window, cx);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
dock_area,
|
dock_area,
|
||||||
@@ -468,6 +465,7 @@ impl Render for IssuesView {
|
|||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.image_cache(image_cache("issues", MAX_IMAGES))
|
||||||
.child(self.render_header(cx))
|
.child(self.render_header(cx))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ use signed_core::Announcement;
|
|||||||
use signed_git::{CommitList, FileCommit};
|
use signed_git::{CommitList, FileCommit};
|
||||||
use signed_state::{GitStore, ProfileStore, RepoStore};
|
use signed_state::{GitStore, ProfileStore, RepoStore};
|
||||||
|
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
|
|
||||||
mod browser;
|
mod browser;
|
||||||
mod commits;
|
mod commits;
|
||||||
mod diff;
|
mod diff;
|
||||||
@@ -145,10 +147,6 @@ impl RepoDetailView {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Owner/maintainer avatars shown by this view stay in the shared
|
|
||||||
// cache until the panel closes; free them then.
|
|
||||||
crate::image_cache::clear_on_release(&cx.entity(), window, cx);
|
|
||||||
|
|
||||||
// The announcement we opened from already carries the repository's
|
// The announcement we opened from already carries the repository's
|
||||||
// NIP-34 `relays` tag, so the store can connect to those relays
|
// NIP-34 `relays` tag, so the store can connect to those relays
|
||||||
// immediately instead of waiting for the bootstrap fetch.
|
// immediately instead of waiting for the bootstrap fetch.
|
||||||
@@ -1239,6 +1237,7 @@ impl Render for RepoDetailView {
|
|||||||
.unwrap_or_else(|| "Overview".into());
|
.unwrap_or_else(|| "Overview".into());
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.image_cache(image_cache("repo", MAX_IMAGES))
|
||||||
.id("repo")
|
.id("repo")
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(self.render_header(cx))
|
.child(self.render_header(cx))
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ use super::helpers::{
|
|||||||
DIFF_ROW_HEIGHT, DiffRow, build_tree_items, diff_rows, find_item, placeholder, render_diff_row,
|
DIFF_ROW_HEIGHT, DiffRow, build_tree_items, diff_rows, find_item, placeholder, render_diff_row,
|
||||||
status_badge, tree_items, tree_row,
|
status_badge, tree_items, tree_row,
|
||||||
};
|
};
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
|
|
||||||
/// Width of the changed-files column.
|
/// Width of the changed-files column.
|
||||||
const TREE_WIDTH: f32 = 260.;
|
const TREE_WIDTH: f32 = 260.;
|
||||||
@@ -91,10 +92,6 @@ impl PullRequestDetailView {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// PR author avatars stay in the shared cache until the panel
|
|
||||||
// closes; free them then.
|
|
||||||
crate::image_cache::clear_on_release(&cx.entity(), window, cx);
|
|
||||||
|
|
||||||
let tree_state = cx.new(|cx| TreeState::new(cx));
|
let tree_state = cx.new(|cx| TreeState::new(cx));
|
||||||
let comment_input =
|
let comment_input =
|
||||||
cx.new(|cx| TextareaState::new(window, cx).placeholder("Leave a comment…"));
|
cx.new(|cx| TextareaState::new(window, cx).placeholder("Leave a comment…"));
|
||||||
@@ -1160,6 +1157,7 @@ impl Focusable for PullRequestDetailView {
|
|||||||
impl Render for PullRequestDetailView {
|
impl Render for PullRequestDetailView {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.image_cache(image_cache("pull-request-detail", MAX_IMAGES))
|
||||||
.id("pull-request-detail")
|
.id("pull-request-detail")
|
||||||
.size_full()
|
.size_full()
|
||||||
.min_h_0()
|
.min_h_0()
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use utils::relative_time;
|
|||||||
|
|
||||||
use super::helpers::{placeholder, status_badge};
|
use super::helpers::{placeholder, status_badge};
|
||||||
use super::pull_request_detail::PullRequestDetailView;
|
use super::pull_request_detail::PullRequestDetailView;
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
|
|
||||||
/// Height of one pull request row in the virtual list: same layout as an
|
/// Height of one pull request row in the virtual list: same layout as an
|
||||||
/// issue row (8px vertical padding (`py_2`) on top and bottom, a 32px title
|
/// issue row (8px vertical padding (`py_2`) on top and bottom, a 32px title
|
||||||
@@ -91,13 +92,9 @@ impl PullRequestsView {
|
|||||||
dock_area: WeakEntity<DockArea>,
|
dock_area: WeakEntity<DockArea>,
|
||||||
store: Entity<RepoStore>,
|
store: Entity<RepoStore>,
|
||||||
repo_name: SharedString,
|
repo_name: SharedString,
|
||||||
window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// PR author avatars stay in the shared cache until the panel
|
|
||||||
// closes; free them then.
|
|
||||||
crate::image_cache::clear_on_release(&cx.entity(), window, cx);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
dock_area,
|
dock_area,
|
||||||
@@ -569,6 +566,7 @@ impl Render for PullRequestsView {
|
|||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.image_cache(image_cache("pull-requests", MAX_IMAGES))
|
||||||
.child(self.render_header(cx))
|
.child(self.render_header(cx))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use signed_state::{ProfileStore, RepoListStore, Timestamp};
|
|||||||
use utils::relative_time;
|
use utils::relative_time;
|
||||||
|
|
||||||
use super::RepoDetailView;
|
use super::RepoDetailView;
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
|
|
||||||
const CARD_HEIGHT: f32 = 160.;
|
const CARD_HEIGHT: f32 = 160.;
|
||||||
|
|
||||||
@@ -201,6 +202,7 @@ impl Render for RepoListView {
|
|||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.relative()
|
.relative()
|
||||||
|
.image_cache(image_cache("repos", MAX_IMAGES))
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use gpui_component::{ActiveTheme, Icon, IconName, Sizable, StyledExt, h_flex, v_
|
|||||||
use signed_state::{Backend, BackendEvent, Profile, ProfileStore};
|
use signed_state::{Backend, BackendEvent, Profile, ProfileStore};
|
||||||
|
|
||||||
use super::RepoListView;
|
use super::RepoListView;
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
|
|
||||||
mod import_dialog;
|
mod import_dialog;
|
||||||
mod onboarding_dialog;
|
mod onboarding_dialog;
|
||||||
@@ -209,6 +210,7 @@ impl Render for SidebarPanel {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
|
.image_cache(image_cache("sidebar", MAX_IMAGES))
|
||||||
.bg(cx.theme().sidebar)
|
.bg(cx.theme().sidebar)
|
||||||
.text_color(cx.theme().sidebar_foreground)
|
.text_color(cx.theme().sidebar_foreground)
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use gpui::{Context, Entity, Render, Subscription, Window, div, px};
|
|||||||
use gpui_component::{Root, StyledExt, Theme};
|
use gpui_component::{Root, StyledExt, Theme};
|
||||||
use signed_state::{Backend, BackendEvent};
|
use signed_state::{Backend, BackendEvent};
|
||||||
|
|
||||||
|
use crate::image_cache::{MAX_IMAGES, image_cache};
|
||||||
use crate::views::SidebarPanel;
|
use crate::views::SidebarPanel;
|
||||||
use crate::views::sidebar::passphrase_dialog;
|
use crate::views::sidebar::passphrase_dialog;
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ impl Render for Workspace {
|
|||||||
let notification_layer = Root::render_notification_layer(window, cx);
|
let notification_layer = Root::render_notification_layer(window, cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.image_cache(crate::image_cache::global(cx))
|
.image_cache(image_cache("workspace", MAX_IMAGES))
|
||||||
.id("workspace")
|
.id("workspace")
|
||||||
.v_flex()
|
.v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|||||||
@@ -51,10 +51,6 @@ fn main() {
|
|||||||
// Sync the theme with the system appearance
|
// Sync the theme with the system appearance
|
||||||
Theme::sync_system_appearance(None, cx);
|
Theme::sync_system_appearance(None, cx);
|
||||||
|
|
||||||
// Install the shared image cache so avatars can be freed when
|
|
||||||
// their views close, instead of staying in memory forever.
|
|
||||||
workspace::image_cache::init(cx);
|
|
||||||
|
|
||||||
// Initialize backend and stores (connects relays, restores session)
|
// Initialize backend and stores (connects relays, restores session)
|
||||||
std::fs::create_dir_all(paths::nostr_dir()).ok();
|
std::fs::create_dir_all(paths::nostr_dir()).ok();
|
||||||
signed_state::init(paths::nostr_dir(), cx);
|
signed_state::init(paths::nostr_dir(), cx);
|
||||||
|
|||||||
Reference in New Issue
Block a user