1use std::fs;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use anyhow::Result;
6use gpui::{
7 actions, div, img, prelude::*, px, rgb, size, App, AppContext, Application, AssetSource,
8 Bounds, Context, ImageSource, KeyBinding, Menu, MenuItem, Point, SharedString, SharedUri,
9 TitlebarOptions, Window, WindowBounds, WindowOptions,
10};
11use reqwest_client::ReqwestClient;
12
13struct Assets {
14 base: PathBuf,
15}
16
17impl AssetSource for Assets {
18 fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
19 fs::read(self.base.join(path))
20 .map(|data| Some(std::borrow::Cow::Owned(data)))
21 .map_err(|e| e.into())
22 }
23
24 fn list(&self, path: &str) -> Result<Vec<SharedString>> {
25 fs::read_dir(self.base.join(path))
26 .map(|entries| {
27 entries
28 .filter_map(|entry| {
29 entry
30 .ok()
31 .and_then(|entry| entry.file_name().into_string().ok())
32 .map(SharedString::from)
33 })
34 .collect()
35 })
36 .map_err(|e| e.into())
37 }
38}
39
40#[derive(IntoElement)]
41struct ImageContainer {
42 text: SharedString,
43 src: ImageSource,
44}
45
46impl ImageContainer {
47 pub fn new(text: impl Into<SharedString>, src: impl Into<ImageSource>) -> Self {
48 Self {
49 text: text.into(),
50 src: src.into(),
51 }
52 }
53}
54
55impl RenderOnce for ImageContainer {
56 fn render(self, _window: &mut Window, _: &mut App) -> impl IntoElement {
57 div().child(
58 div()
59 .flex_row()
60 .size_full()
61 .gap_4()
62 .child(self.text)
63 .child(img(self.src).size(px(256.0))),
64 )
65 }
66}
67
68struct ImageShowcase {
69 local_resource: Arc<std::path::Path>,
70 remote_resource: SharedUri,
71 asset_resource: SharedString,
72}
73
74impl Render for ImageShowcase {
75 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
76 div()
77 .size_full()
78 .flex()
79 .flex_col()
80 .justify_center()
81 .items_center()
82 .gap_8()
83 .bg(rgb(0xffffff))
84 .child(
85 div()
86 .flex()
87 .flex_row()
88 .justify_center()
89 .items_center()
90 .gap_8()
91 .child(ImageContainer::new(
92 "Image loaded from a local file",
93 self.local_resource.clone(),
94 ))
95 .child(ImageContainer::new(
96 "Image loaded from a remote resource",
97 self.remote_resource.clone(),
98 ))
99 .child(ImageContainer::new(
100 "Image loaded from an asset",
101 self.asset_resource.clone(),
102 )),
103 )
104 .child(
105 div()
106 .flex()
107 .flex_row()
108 .gap_8()
109 .child(
110 div()
111 .flex_col()
112 .child("Auto Width")
113 .child(img("https://picsum.photos/800/400").h(px(180.))),
114 )
115 .child(
116 div()
117 .flex_col()
118 .child("Auto Height")
119 .child(img("https://picsum.photos/480/640").w(px(180.))),
120 ),
121 )
122 }
123}
124
125actions!(image, [Quit]);
126
127fn main() {
128 env_logger::init();
129
130 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
131
132 Application::new()
133 .with_assets(Assets {
134 base: manifest_dir.join("examples"),
135 })
136 .run(move |cx: &mut App| {
137 let http_client = ReqwestClient::user_agent("gpui example").unwrap();
138 cx.set_http_client(Arc::new(http_client));
139
140 cx.activate(true);
141 cx.on_action(|_: &Quit, cx| cx.quit());
142 cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
143 cx.set_menus(vec![Menu {
144 name: "Image".into(),
145 items: vec![MenuItem::action("Quit", Quit)],
146 }]);
147
148 let window_options = WindowOptions {
149 titlebar: Some(TitlebarOptions {
150 title: Some(SharedString::from("Image Example")),
151 appears_transparent: false,
152 ..Default::default()
153 }),
154
155 window_bounds: Some(WindowBounds::Windowed(Bounds {
156 size: size(px(1100.), px(600.)),
157 origin: Point::new(px(200.), px(200.)),
158 })),
159
160 ..Default::default()
161 };
162
163 cx.open_window(window_options, |_, cx| {
164 cx.new(|_| ImageShowcase {
165 // Relative path to your root project path
166 local_resource: manifest_dir.join("examples/image/app-icon.png").into(),
167
168 remote_resource: "https://picsum.photos/512/512".into(),
169
170 asset_resource: "image/color.svg".into(),
171 })
172 })
173 .unwrap();
174 });
175}