Add WebView (#2)
- https://github.com/huacnlee/zed/pull/6 - https://github.com/zed-industries/zed/pull/13730
This commit is contained in:
parent
91e123e942
commit
f6146220e4
14 changed files with 998 additions and 83 deletions
710
Cargo.lock
generated
710
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -5,8 +5,7 @@ default-members = ["crates/app"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# gpui = { path = "/Users/jason/github/zed/crates/gpui" }
|
gpui = { git = "https://github.com/huacnlee/zed.git", branch = "export-platform-window" }
|
||||||
gpui = { git = "https://github.com/zed-industries/zed.git" }
|
|
||||||
ui = { path = "crates/ui" }
|
ui = { path = "crates/ui" }
|
||||||
story = { path = "crates/story" }
|
story = { path = "crates/story" }
|
||||||
workspace = { path = "crates/workspace" }
|
workspace = { path = "crates/workspace" }
|
||||||
|
|
|
||||||
4
assets/icons/arrow-left.svg
Normal file
4
assets/icons/arrow-left.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-left">
|
||||||
|
<path d="m12 19-7-7 7-7"/>
|
||||||
|
<path d="M19 12H5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 273 B |
4
assets/icons/arrow-right.svg
Normal file
4
assets/icons/arrow-right.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right">
|
||||||
|
<path d="M5 12h14"/>
|
||||||
|
<path d="m12 5 7 7-7 7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 273 B |
3
assets/icons/chevron-left.svg
Normal file
3
assets/icons/chevron-left.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left">
|
||||||
|
<path d="m15 18-6-6 6-6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 252 B |
3
assets/icons/chevron-right.svg
Normal file
3
assets/icons/chevron-right.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right">
|
||||||
|
<path d="m9 18 6-6-6-6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 252 B |
|
|
@ -3,7 +3,7 @@ use prelude::FluentBuilder as _;
|
||||||
use story::{
|
use story::{
|
||||||
ButtonStory, CheckboxStory, DropdownStory, ImageStory, InputStory, ListStory, PickerStory,
|
ButtonStory, CheckboxStory, DropdownStory, ImageStory, InputStory, ListStory, PickerStory,
|
||||||
PopoverStory, ProgressStory, ResizableStory, ScrollableStory, StoryContainer, SwitchStory,
|
PopoverStory, ProgressStory, ResizableStory, ScrollableStory, StoryContainer, SwitchStory,
|
||||||
TableStory, TooltipStory,
|
TableStory, TooltipStory, WebViewStory,
|
||||||
};
|
};
|
||||||
use workspace::{dock::DockPosition, TitleBar, Workspace};
|
use workspace::{dock::DockPosition, TitleBar, Workspace};
|
||||||
|
|
||||||
|
|
@ -50,21 +50,23 @@ impl StoryWorkspace {
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
StoryContainer::add_panel(
|
StoryContainer::add_pane(
|
||||||
|
"Input",
|
||||||
|
"A control that allows the user to input text.",
|
||||||
InputStory::view(cx).into(),
|
InputStory::view(cx).into(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
DockPosition::Right,
|
|
||||||
px(350.0),
|
|
||||||
cx,
|
cx,
|
||||||
);
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
StoryContainer::add_panel(
|
StoryContainer::add_pane(
|
||||||
|
"Checkbox",
|
||||||
|
"A control that allows the user to toggle between checked and not checked.",
|
||||||
CheckboxStory::view(cx).into(),
|
CheckboxStory::view(cx).into(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
DockPosition::Bottom,
|
|
||||||
px(200.),
|
|
||||||
cx,
|
cx,
|
||||||
);
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
StoryContainer::add_pane(
|
StoryContainer::add_pane(
|
||||||
"Switch",
|
"Switch",
|
||||||
|
|
@ -128,6 +130,14 @@ impl StoryWorkspace {
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
StoryContainer::add_panel(
|
||||||
|
WebViewStory::view(cx).into(),
|
||||||
|
workspace.clone(),
|
||||||
|
DockPosition::Right,
|
||||||
|
px(450.),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
StoryContainer::add_pane(
|
StoryContainer::add_pane(
|
||||||
"Table",
|
"Table",
|
||||||
"Powerful table and datagrids built.",
|
"Powerful table and datagrids built.",
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,7 @@ anyhow = "1"
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
charts-rs = "0.3"
|
charts-rs = "0.3"
|
||||||
image = "0.25.1"
|
image = "0.25.1"
|
||||||
|
wry = "0"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ mod scrollable_story;
|
||||||
mod switch_story;
|
mod switch_story;
|
||||||
mod table_story;
|
mod table_story;
|
||||||
mod tooltip_story;
|
mod tooltip_story;
|
||||||
|
mod webview_story;
|
||||||
|
|
||||||
pub use button_story::ButtonStory;
|
pub use button_story::ButtonStory;
|
||||||
pub use checkbox_story::CheckboxStory;
|
pub use checkbox_story::CheckboxStory;
|
||||||
|
|
@ -27,6 +28,7 @@ pub use scrollable_story::ScrollableStory;
|
||||||
pub use switch_story::SwitchStory;
|
pub use switch_story::SwitchStory;
|
||||||
pub use table_story::TableStory;
|
pub use table_story::TableStory;
|
||||||
pub use tooltip_story::TooltipStory;
|
pub use tooltip_story::TooltipStory;
|
||||||
|
pub use webview_story::WebViewStory;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter,
|
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter,
|
||||||
|
|
@ -46,14 +48,6 @@ pub fn init(cx: &mut AppContext) {
|
||||||
input_story::init(cx);
|
input_story::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn story_case(
|
|
||||||
name: &'static str,
|
|
||||||
description: &'static str,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> StoryContainer {
|
|
||||||
StoryContainer::new(name, description, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn section(title: impl Into<SharedString>, cx: &WindowContext) -> Div {
|
pub fn section(title: impl Into<SharedString>, cx: &WindowContext) -> Div {
|
||||||
use ui::theme::ActiveTheme;
|
use ui::theme::ActiveTheme;
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
|
@ -104,7 +98,7 @@ impl Item for StoryContainer {
|
||||||
Label::new(self.name.clone()).into_any_element()
|
Label::new(self.name.clone()).into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivated(&mut self, _: &mut ViewContext<Self>) {
|
fn deactivated(&mut self, _cx: &mut ViewContext<Self>) {
|
||||||
self.active = false;
|
self.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
105
crates/story/src/webview_story.rs
Normal file
105
crates/story/src/webview_story.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
use gpui::{
|
||||||
|
div, ClickEvent, FocusHandle, FocusableView, ParentElement as _, Render, Styled as _, View,
|
||||||
|
ViewContext, VisualContext as _, WindowContext,
|
||||||
|
};
|
||||||
|
use ui::{
|
||||||
|
button::Button,
|
||||||
|
h_flex,
|
||||||
|
input::{TextEvent, TextInput},
|
||||||
|
theme::ActiveTheme,
|
||||||
|
v_flex,
|
||||||
|
webview::WebView,
|
||||||
|
Clickable, IconName,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct WebViewStory {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
webview: View<WebView>,
|
||||||
|
address_input: View<TextInput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebViewStory {
|
||||||
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
|
let webview = cx.new_view(|cx| WebView::new(cx));
|
||||||
|
|
||||||
|
let address_input = cx.new_view(|cx| {
|
||||||
|
let mut input = TextInput::new(cx);
|
||||||
|
input.set_text("https://github.com/explore", cx);
|
||||||
|
input
|
||||||
|
});
|
||||||
|
|
||||||
|
let url = address_input.read(cx).text();
|
||||||
|
webview.update(cx, |view, _| {
|
||||||
|
view.load_url(&url);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.new_view(|cx| {
|
||||||
|
let this = WebViewStory {
|
||||||
|
focus_handle,
|
||||||
|
webview,
|
||||||
|
address_input: address_input.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.subscribe(
|
||||||
|
&address_input,
|
||||||
|
|this: &mut Self, input, event: &TextEvent, cx| match event {
|
||||||
|
TextEvent::PressEnter => {
|
||||||
|
let url = input.read(cx).text();
|
||||||
|
this.webview.update(cx, |view, _| {
|
||||||
|
view.load_url(&url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
this
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide(&self, cx: &mut WindowContext) {
|
||||||
|
self.webview.update(cx, |webview, _| webview.hide())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_back(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
|
self.webview.update(cx, |webview, _| {
|
||||||
|
webview.back().unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusableView for WebViewStory {
|
||||||
|
fn focus_handle(&self, _cx: &gpui::AppContext) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for WebViewStory {
|
||||||
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.p_2()
|
||||||
|
.gap_3()
|
||||||
|
.size_full()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.items_center()
|
||||||
|
.child(
|
||||||
|
Button::new("go-back", cx)
|
||||||
|
.icon(IconName::ArrowLeft)
|
||||||
|
.on_click(cx.listener(Self::go_back)),
|
||||||
|
)
|
||||||
|
.child(self.address_input.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.child(self.webview.clone()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ anyhow = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = "1.0.203"
|
serde = "1.0.203"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
wry = "0"
|
|
||||||
smallvec = "1.13.2"
|
smallvec = "1.13.2"
|
||||||
windows = "0.57.0"
|
windows = "0.57.0"
|
||||||
unicode-segmentation = "1.11.0"
|
unicode-segmentation = "1.11.0"
|
||||||
|
|
@ -24,6 +24,12 @@ usvg = { version = "0.41.0", default-features = false }
|
||||||
taffy = "0.4.3"
|
taffy = "0.4.3"
|
||||||
paste = "1"
|
paste = "1"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
|
raw-window-handle = "0.6.2"
|
||||||
|
winit = "0.30.3"
|
||||||
|
objc = "0.2"
|
||||||
|
objc_id = "0.1"
|
||||||
|
cocoa = "0.25"
|
||||||
|
wry = "0"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ pub enum IconName {
|
||||||
CircleX,
|
CircleX,
|
||||||
Loader,
|
Loader,
|
||||||
LoaderCircle,
|
LoaderCircle,
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowRight,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconName {
|
impl IconName {
|
||||||
|
|
@ -45,6 +49,10 @@ impl IconName {
|
||||||
IconName::CircleX => "icons/circle-x.svg",
|
IconName::CircleX => "icons/circle-x.svg",
|
||||||
IconName::Loader => "icons/loader.svg",
|
IconName::Loader => "icons/loader.svg",
|
||||||
IconName::LoaderCircle => "icons/loader-circle.svg",
|
IconName::LoaderCircle => "icons/loader-circle.svg",
|
||||||
|
IconName::ArrowLeft => "icons/arrow-left.svg",
|
||||||
|
IconName::ArrowRight => "icons/arrow-right.svg",
|
||||||
|
IconName::ChevronLeft => "icons/chevron-left.svg",
|
||||||
|
IconName::ChevronRight => "icons/chevron-right.svg",
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ pub mod tab;
|
||||||
pub mod table;
|
pub mod table;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
pub mod tooltip;
|
pub mod tooltip;
|
||||||
|
pub mod webview;
|
||||||
|
|
||||||
pub use clickable::Clickable;
|
pub use clickable::Clickable;
|
||||||
pub use disableable::Disableable;
|
pub use disableable::Disableable;
|
||||||
|
|
@ -52,4 +53,5 @@ pub fn init(cx: &mut gpui::AppContext) {
|
||||||
popover::init(cx);
|
popover::init(cx);
|
||||||
popup_menu::init(cx);
|
popup_menu::init(cx);
|
||||||
table::init(cx);
|
table::init(cx);
|
||||||
|
webview::init(cx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
187
crates/ui/src/webview.rs
Normal file
187
crates/ui/src/webview.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
use std::{ops::Deref, rc::Rc};
|
||||||
|
|
||||||
|
use wry::{
|
||||||
|
dpi::{self, LogicalSize},
|
||||||
|
Rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
div, AppContext, Bounds, ContentMask, DismissEvent, Element, ElementId, EventEmitter,
|
||||||
|
FocusHandle, FocusableView, GlobalElementId, Hitbox, InteractiveElement, IntoElement, LayoutId,
|
||||||
|
MouseDownEvent, ParentElement as _, Pixels, Render, Size, Style, Styled as _, View,
|
||||||
|
WindowContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(_cx: &AppContext) {}
|
||||||
|
|
||||||
|
pub struct WebView {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
webview: Rc<wry::WebView>,
|
||||||
|
visable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebView {
|
||||||
|
pub fn new(cx: &mut WindowContext) -> Self {
|
||||||
|
let focus_handle = cx.focus_handle();
|
||||||
|
let window_handle = cx.raw_window_handle();
|
||||||
|
|
||||||
|
let webview = wry::WebView::new_as_child(&window_handle)
|
||||||
|
.expect("failed to create webview to child window");
|
||||||
|
let _ = webview.set_bounds(Rect::default());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
focus_handle,
|
||||||
|
visable: true,
|
||||||
|
webview: Rc::new(webview),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&mut self) {
|
||||||
|
let _ = self.webview.set_visible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide(&mut self) {
|
||||||
|
let _ = self.webview.set_visible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visible(&self) -> bool {
|
||||||
|
self.visable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Go back in the webview history.
|
||||||
|
pub fn back(&mut self) -> anyhow::Result<()> {
|
||||||
|
Ok(self.webview.evaluate_script("history.back();")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_url(&mut self, url: &str) {
|
||||||
|
self.webview.load_url(url).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for WebView {
|
||||||
|
type Target = wry::WebView;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.webview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusableView for WebView {
|
||||||
|
fn focus_handle(&self, _cx: &gpui::AppContext) -> FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<DismissEvent> for WebView {}
|
||||||
|
|
||||||
|
impl Render for WebView {
|
||||||
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let view = cx.view().clone();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.size_full()
|
||||||
|
.child(WebViewElement::new(self.webview.clone(), view, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A webview element can display a wry webview.
|
||||||
|
pub struct WebViewElement {
|
||||||
|
parent: View<WebView>,
|
||||||
|
view: Rc<wry::WebView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebViewElement {
|
||||||
|
/// Create a new webview element from a wry WebView.
|
||||||
|
pub fn new(view: Rc<wry::WebView>, parent: View<WebView>, _: &mut WindowContext) -> Self {
|
||||||
|
Self { view, parent }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoElement for WebViewElement {
|
||||||
|
type Element = WebViewElement;
|
||||||
|
|
||||||
|
fn into_element(self) -> Self::Element {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for WebViewElement {
|
||||||
|
type RequestLayoutState = ();
|
||||||
|
type PrepaintState = Option<Hitbox>;
|
||||||
|
|
||||||
|
fn id(&self) -> Option<ElementId> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_layout(
|
||||||
|
&mut self,
|
||||||
|
_: Option<&GlobalElementId>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> (LayoutId, Self::RequestLayoutState) {
|
||||||
|
let mut style = Style::default();
|
||||||
|
style.flex_grow = 0.0;
|
||||||
|
style.flex_shrink = 1.;
|
||||||
|
style.size = Size::full();
|
||||||
|
// If the parent view is no longer visible, we don't need to layout the webview
|
||||||
|
|
||||||
|
let id = cx.request_layout(style, []);
|
||||||
|
(id, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepaint(
|
||||||
|
&mut self,
|
||||||
|
_: Option<&GlobalElementId>,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
_: &mut Self::RequestLayoutState,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Self::PrepaintState {
|
||||||
|
if !self.parent.read(cx).visible() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.view
|
||||||
|
.set_bounds(Rect {
|
||||||
|
size: dpi::Size::Logical(LogicalSize {
|
||||||
|
width: (bounds.size.width.0).into(),
|
||||||
|
height: (bounds.size.height.0).into(),
|
||||||
|
}),
|
||||||
|
position: dpi::Position::Logical(dpi::LogicalPosition::new(
|
||||||
|
bounds.origin.x.into(),
|
||||||
|
bounds.origin.y.into(),
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Create a hitbox to handle mouse event
|
||||||
|
Some(cx.insert_hitbox(bounds, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
_: Option<&GlobalElementId>,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
_: &mut Self::RequestLayoutState,
|
||||||
|
hitbox: &mut Self::PrepaintState,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) {
|
||||||
|
let bounds = hitbox.clone().map(|h| h.bounds).unwrap_or(bounds);
|
||||||
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
|
let webview = self.view.clone();
|
||||||
|
cx.on_mouse_event(move |event: &MouseDownEvent, _, cx| {
|
||||||
|
if !bounds.contains(&event.position) {
|
||||||
|
// Click white space to blur the input focus
|
||||||
|
webview
|
||||||
|
.evaluate_script(
|
||||||
|
r#"
|
||||||
|
document.querySelectorAll("input,textarea").forEach(input => input.blur());
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("failed to evaluate_script to blur input");
|
||||||
|
} else {
|
||||||
|
cx.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue