Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+214
View File
@@ -0,0 +1,214 @@
use std::sync::Arc;
use pathfinder_geometry::vector::Vector2F;
use ui_components::{lightbox, Component as _};
use warpui::assets::asset_cache::{AssetCache, AssetSource, AssetState};
use warpui::image_cache::ImageType;
use warpui::keymap::{FixedBinding, Keystroke};
use warpui::prelude::*;
use warpui::{AppContext, BlurContext, Element, Entity, SingletonEntity, View, ViewContext};
use crate::appearance::Appearance;
pub use lightbox::LightboxImage;
pub fn init(app: &mut AppContext) {
use warpui::keymap::macros::*;
let view_id = id!(LightboxView::ui_name());
app.register_fixed_bindings([
FixedBinding::new("escape", LightboxViewAction::Dismiss, view_id.clone()),
FixedBinding::new(
"left",
LightboxViewAction::NavigatePrevious,
view_id.clone(),
),
FixedBinding::new("right", LightboxViewAction::NavigateNext, view_id),
]);
}
/// Parameters needed to open a lightbox.
#[derive(Clone, Debug)]
pub struct LightboxParams {
/// The images to display in the lightbox.
pub images: Vec<LightboxImage>,
/// The index of the image to display initially.
pub initial_index: usize,
}
/// Events emitted by the `LightboxView` to its parent.
pub enum LightboxViewEvent {
/// The user explicitly dismissed the lightbox (Escape, close button, or scrim click).
Close,
/// Focus left the lightbox subtree (e.g. the user switched tabs).
FocusLost,
}
impl Entity for LightboxView {
type Event = LightboxViewEvent;
}
/// Actions dispatched within the `LightboxView`.
#[derive(Debug)]
pub enum LightboxViewAction {
/// Dismiss the lightbox (triggered by clicking outside, close button, or Escape).
Dismiss,
/// Navigate to the previous image.
NavigatePrevious,
/// Navigate to the next image.
NavigateNext,
}
/// A view that renders a full-window lightbox overlay.
pub struct LightboxView {
params: LightboxParams,
current_index: usize,
lightbox: lightbox::Lightbox,
}
impl LightboxView {
pub fn new(params: LightboxParams, ctx: &mut ViewContext<Self>) -> Self {
let initial_index = params
.initial_index
.min(params.images.len().saturating_sub(1));
let view = Self {
params,
current_index: initial_index,
lightbox: lightbox::Lightbox::default(),
};
view.start_asset_loads(ctx);
view
}
/// Replace the images and navigate to the given initial index.
pub fn update_params(&mut self, params: LightboxParams, ctx: &mut ViewContext<Self>) {
let initial_index = params
.initial_index
.min(params.images.len().saturating_sub(1));
self.params = params;
self.current_index = initial_index;
self.start_asset_loads(ctx);
}
/// Update a single image at the given index without replacing the full list.
pub fn update_image_at(
&mut self,
index: usize,
image: LightboxImage,
ctx: &mut ViewContext<Self>,
) {
if let Some(slot) = self.params.images.get_mut(index) {
if let lightbox::LightboxImageSource::Resolved { ref asset_source } = image.source {
Self::start_asset_load(asset_source, ctx);
}
*slot = image;
}
}
/// Kick off asset loads for all `Resolved` images and schedule re-renders.
fn start_asset_loads(&self, ctx: &mut ViewContext<Self>) {
for img in &self.params.images {
if let lightbox::LightboxImageSource::Resolved { ref asset_source } = img.source {
Self::start_asset_load(asset_source, ctx);
}
}
}
/// Eagerly load a single asset and schedule a `ctx.notify()` when the fetch
/// completes so the lightbox re-renders with the loaded image.
fn start_asset_load(asset_source: &AssetSource, ctx: &mut ViewContext<Self>) {
let asset_cache = AssetCache::as_ref(ctx);
if let AssetState::Loading { handle } =
asset_cache.load_asset::<ImageType>(asset_source.clone())
{
if let Some(future) = handle.when_loaded(asset_cache) {
ctx.spawn(future, |_me, (), ctx| {
ctx.notify();
});
}
}
}
}
impl View for LightboxView {
fn ui_name() -> &'static str {
"LightboxView"
}
fn on_blur(&mut self, _blur_ctx: &BlurContext, ctx: &mut ViewContext<Self>) {
// Only dismiss if focus has left the entire lightbox subtree.
if !ctx.is_self_or_child_focused() {
ctx.emit(LightboxViewEvent::FocusLost);
}
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
// Determine the native pixel size of the current image by querying the
// asset cache. This will be `Some` once the image bytes have been fully
// loaded and decoded.
let current_image_native_size =
self.params
.images
.get(self.current_index)
.and_then(|img| match &img.source {
lightbox::LightboxImageSource::Resolved { asset_source } => {
let asset_cache = AssetCache::as_ref(app);
match asset_cache.load_asset::<ImageType>(asset_source.clone()) {
AssetState::Loaded { data } => data
.image_size()
.map(|size| Vector2F::new(size.x() as f32, size.y() as f32)),
_ => None,
}
}
lightbox::LightboxImageSource::Loading => None,
});
self.lightbox.render(
appearance,
lightbox::Params {
images: &self.params.images,
current_index: self.current_index,
on_dismiss: Arc::new(|ctx, _| {
ctx.dispatch_typed_action(LightboxViewAction::Dismiss);
}),
current_image_native_size,
options: lightbox::Options {
dismiss_keystroke: Keystroke::parse("escape").ok(),
on_navigate: Some(Arc::new(|direction, ctx, _| match direction {
lightbox::NavigationDirection::Previous => {
ctx.dispatch_typed_action(LightboxViewAction::NavigatePrevious);
}
lightbox::NavigationDirection::Next => {
ctx.dispatch_typed_action(LightboxViewAction::NavigateNext);
}
})),
},
},
)
}
}
impl TypedActionView for LightboxView {
type Action = LightboxViewAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
LightboxViewAction::Dismiss => {
ctx.emit(LightboxViewEvent::Close);
}
LightboxViewAction::NavigatePrevious => {
if self.current_index > 0 {
self.current_index -= 1;
ctx.notify();
}
}
LightboxViewAction::NavigateNext => {
if self.current_index + 1 < self.params.images.len() {
self.current_index += 1;
ctx.notify();
}
}
}
}
}