use anyhow::{Context, anyhow};use db::kvp::KeyValueStore;use gpui::App;use ui::Window; use crate::preview;use crate::shape::{self, Centered, RelativeHeight, RelativeWidth, Shape, ViewportFraction}; const PICKERS_NAMESPACE: &str = "pickers_v2"; pub(crate) fn store_shape_for_this_layout( picker_delegate: &'static str, preview_layout: Option<preview::Layout>, shape: shape::Centered, window: &ui::Window, cx: &App,) { let shape = PickerConfig::from_centered(shape, window); let kvp = KeyValueStore::global(cx); db::write_and_log(cx, async move || { kvp.scoped(PICKERS_NAMESPACE) .write( shape_key(picker_delegate, preview_layout), serde_json::to_string(&shape).context("Could not serialize size of picker")?, ) .await?; // Must be written after the shape or loading can break // if this future is ever cancelled. kvp.scoped(PICKERS_NAMESPACE) .write( last_layout_key(picker_delegate), layout_as_str(preview_layout).to_string(), ) .await });} /// Persist the last-used preview layout without touching the stored shape.////// Changing the preview position (e.g. right/below/hidden) without resizing/// must still be remembered, so this is called whenever the layout changes.pub(crate) fn store_last_layout( picker_delegate: &'static str, preview_layout: Option<preview::Layout>, cx: &App,) { let kvp = KeyValueStore::global(cx); db::write_and_log(cx, async move || { kvp.scoped(PICKERS_NAMESPACE) .write( last_layout_key(picker_delegate), layout_as_str(preview_layout).to_string(), ) .await });} pub(crate) fn try_load_shape( picker_delegate: &'static str, preview_layout: impl Into<Option<preview::Layout>>, cx: &App,) -> anyhow::Result<Option<Shape>> { let Some(shape) = KeyValueStore::global(cx) .scoped(PICKERS_NAMESPACE) .read(&shape_key(picker_delegate, preview_layout.into())) .context("Could not read picker shape from KeyValueStore")? else { return Ok(None); }; let shape = serde_json::from_str::<PickerConfig>(&shape) .context("Could not deserialize loaded picker shape from persistence")? .into_centered(); Ok(Some(Shape::HorizontallyCentered(shape)))} pub(crate) fn load_last_preview_layout( picker_delegate: &'static str, cx: &App,) -> anyhow::Result<Option<preview::Layout>> { let Some(last_layout) = KeyValueStore::global(cx) .scoped(PICKERS_NAMESPACE) .read(&last_layout_key(picker_delegate)) .context("Could not read last picker layout from KeyValueStore")? else { return Ok(None); }; parse_layout(&last_layout)} fn shape_key(picker_delegate: &'static str, preview_layout: Option<preview::Layout>) -> String { format!("{picker_delegate}/{}", layout_as_str(preview_layout))} fn last_layout_key(picker_delegate: &'static str) -> String { format!("{picker_delegate}/LAST_PREVIEW_LAYOUT")} fn layout_as_str(layout: Option<preview::Layout>) -> &'static str { match layout { Some(preview::Layout::Hidden) => "hidden", Some(preview::Layout::Below) => "below", Some(preview::Layout::Right) => "right", None => "none", }} fn parse_layout(s: &str) -> anyhow::Result<Option<preview::Layout>> { Ok(Some(match s { "hidden" => preview::Layout::Hidden, "below" => preview::Layout::Below, "right" => preview::Layout::Right, "none" => return Ok(None), _ => return Err(anyhow!("Unknown layout: `{}`", s)), }))} /// A resized picker for persisting its size. All values are stored as fractions/// of the viewport so they remain meaningful across window sizes.#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]pub(crate) struct PickerConfig { width: f32, // relative fraction of viewport height: f32, // relative fraction of viewport preview_size: f32, // relative fraction of viewport} impl PickerConfig { pub(crate) fn from_centered(shape: Centered, window: &Window) -> Self { PickerConfig { width: shape.width.as_viewport_fraction(window).raw(), height: shape.height.as_viewport_fraction(window).raw(), preview_size: shape.preview_size.raw(), } } pub(crate) fn into_centered(self) -> Centered { Centered { width: RelativeWidth::viewport(self.width.clamp(0.0, 1.0)), height: RelativeHeight::viewport(self.height.clamp(0.0, 1.0)), preview_size: ViewportFraction::fraction(self.preview_size.clamp(0.0, 1.0)), } }}
use anyhow::{Context, anyhow};
use db::kvp::KeyValueStore;
use gpui::App;
use ui::Window;
use crate::preview;
use crate::shape::{self, Centered, RelativeHeight, RelativeWidth, Shape, ViewportFraction};
const PICKERS_NAMESPACE: &str = "pickers_v2";
pub(crate) fn store_shape_for_this_layout(
picker_delegate: &'static str,
preview_layout: Option<preview::Layout>,
shape: shape::Centered,
window: &ui::Window,
cx: &App,
) {
let shape = PickerConfig::from_centered(shape, window);
let kvp = KeyValueStore::global(cx);
db::write_and_log(cx, async move || {
kvp.scoped(PICKERS_NAMESPACE)
.write(
shape_key(picker_delegate, preview_layout),
serde_json::to_string(&shape).context("Could not serialize size of picker")?,
)
.await?;
// Must be written after the shape or loading can break
// if this future is ever cancelled.
kvp.scoped(PICKERS_NAMESPACE)
.write(
last_layout_key(picker_delegate),
layout_as_str(preview_layout).to_string(),
)
.await
});
}
/// Persist the last-used preview layout without touching the stored shape.
///
/// Changing the preview position (e.g. right/below/hidden) without resizing
/// must still be remembered, so this is called whenever the layout changes.
pub(crate) fn store_last_layout(
picker_delegate: &'static str,
preview_layout: Option<preview::Layout>,
cx: &App,
) {
let kvp = KeyValueStore::global(cx);
db::write_and_log(cx, async move || {
kvp.scoped(PICKERS_NAMESPACE)
.write(
last_layout_key(picker_delegate),
layout_as_str(preview_layout).to_string(),
)
.await
});
}
pub(crate) fn try_load_shape(
picker_delegate: &'static str,
preview_layout: impl Into<Option<preview::Layout>>,
cx: &App,
) -> anyhow::Result<Option<Shape>> {
let Some(shape) = KeyValueStore::global(cx)
.scoped(PICKERS_NAMESPACE)
.read(&shape_key(picker_delegate, preview_layout.into()))
.context("Could not read picker shape from KeyValueStore")?
else {
return Ok(None);
};
let shape = serde_json::from_str::<PickerConfig>(&shape)
.context("Could not deserialize loaded picker shape from persistence")?
.into_centered();
Ok(Some(Shape::HorizontallyCentered(shape)))
}
pub(crate) fn load_last_preview_layout(
picker_delegate: &'static str,
cx: &App,
) -> anyhow::Result<Option<preview::Layout>> {
let Some(last_layout) = KeyValueStore::global(cx)
.scoped(PICKERS_NAMESPACE)
.read(&last_layout_key(picker_delegate))
.context("Could not read last picker layout from KeyValueStore")?
else {
return Ok(None);
};
parse_layout(&last_layout)
}
fn shape_key(picker_delegate: &'static str, preview_layout: Option<preview::Layout>) -> String {
format!("{picker_delegate}/{}", layout_as_str(preview_layout))
}
fn last_layout_key(picker_delegate: &'static str) -> String {
format!("{picker_delegate}/LAST_PREVIEW_LAYOUT")
}
fn layout_as_str(layout: Option<preview::Layout>) -> &'static str {
match layout {
Some(preview::Layout::Hidden) => "hidden",
Some(preview::Layout::Below) => "below",
Some(preview::Layout::Right) => "right",
None => "none",
}
}
fn parse_layout(s: &str) -> anyhow::Result<Option<preview::Layout>> {
Ok(Some(match s {
"hidden" => preview::Layout::Hidden,
"below" => preview::Layout::Below,
"right" => preview::Layout::Right,
"none" => return Ok(None),
_ => return Err(anyhow!("Unknown layout: `{}`", s)),
}))
}
/// A resized picker for persisting its size. All values are stored as fractions
/// of the viewport so they remain meaningful across window sizes.
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]
pub(crate) struct PickerConfig {
width: f32, // relative fraction of viewport
height: f32, // relative fraction of viewport
preview_size: f32, // relative fraction of viewport
}
impl PickerConfig {
pub(crate) fn from_centered(shape: Centered, window: &Window) -> Self {
PickerConfig {
width: shape.width.as_viewport_fraction(window).raw(),
height: shape.height.as_viewport_fraction(window).raw(),
preview_size: shape.preview_size.raw(),
}
}
pub(crate) fn into_centered(self) -> Centered {
Centered {
width: RelativeWidth::viewport(self.width.clamp(0.0, 1.0)),
height: RelativeHeight::viewport(self.height.clamp(0.0, 1.0)),
preview_size: ViewportFraction::fraction(self.preview_size.clamp(0.0, 1.0)),
}
}
}