use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct WebViewState {
pub url: String,
pub title: Option<String>,
pub loading: bool,
pub can_go_back: bool,
pub can_go_forward: bool,
pub zoom: f32,
pub error: Option<String>,
}
impl WebViewState {
#[must_use]
pub fn new() -> Self {
Self {
url: String::new(),
title: None,
loading: false,
can_go_back: false,
can_go_forward: false,
zoom: 1.0,
error: None,
}
}
#[must_use]
pub fn with_url(url: impl Into<String>) -> Self {
Self {
url: url.into(),
..Self::new()
}
}
pub fn set_url(&mut self, url: impl Into<String>) {
self.url = url.into();
self.error = None;
}
pub fn set_title(&mut self, title: impl Into<String>) {
self.title = Some(title.into());
}
pub fn set_loading(&mut self, loading: bool) {
self.loading = loading;
}
pub fn set_navigation(&mut self, can_back: bool, can_forward: bool) {
self.can_go_back = can_back;
self.can_go_forward = can_forward;
}
pub fn set_zoom(&mut self, zoom: f32) {
self.zoom = zoom.clamp(0.25, 5.0);
}
pub fn zoom_in(&mut self) {
self.set_zoom(self.zoom + 0.1);
}
pub fn zoom_out(&mut self) {
self.set_zoom(self.zoom - 0.1);
}
pub fn reset_zoom(&mut self) {
self.zoom = 1.0;
}
pub fn set_error(&mut self, error: impl Into<String>) {
self.error = Some(error.into());
self.loading = false;
}
}
#[derive(Debug, Clone)]
pub enum WebViewCommand {
Navigate(String),
GoBack,
GoForward,
Reload,
Stop,
ExecuteScript(String),
SetZoom(f32),
LoadHtml(String),
}
#[derive(Debug, Clone)]
pub struct WebViewConfig {
pub initial_url: Option<String>,
pub user_agent: Option<String>,
pub javascript_enabled: bool,
pub storage_enabled: bool,
pub headers: HashMap<String, String>,
pub background_color: Option<String>,
pub devtools: bool,
pub file_access: bool,
}
impl Default for WebViewConfig {
fn default() -> Self {
Self {
initial_url: None,
user_agent: None,
javascript_enabled: true,
storage_enabled: true,
headers: HashMap::new(),
background_color: None,
devtools: false,
file_access: false,
}
}
}
impl WebViewConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn url(mut self, url: impl Into<String>) -> Self {
self.initial_url = Some(url.into());
self
}
#[must_use]
pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
self.user_agent = Some(agent.into());
self
}
#[must_use]
pub fn disable_javascript(mut self) -> Self {
self.javascript_enabled = false;
self
}
#[must_use]
pub fn disable_storage(mut self) -> Self {
self.storage_enabled = false;
self
}
#[must_use]
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
#[must_use]
pub fn background(mut self, color: impl Into<String>) -> Self {
self.background_color = Some(color.into());
self
}
#[must_use]
pub fn with_devtools(mut self) -> Self {
self.devtools = true;
self
}
#[must_use]
pub fn with_file_access(mut self) -> Self {
self.file_access = true;
self
}
}
pub struct BrowserBar<'a, Message> {
state: &'a WebViewState,
on_navigate: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_back: Option<Message>,
on_forward: Option<Message>,
on_reload: Option<Message>,
on_stop: Option<Message>,
}
impl<'a, Message> BrowserBar<'a, Message>
where
Message: Clone,
{
pub fn new(state: &'a WebViewState) -> Self {
Self {
state,
on_navigate: None,
on_back: None,
on_forward: None,
on_reload: None,
on_stop: None,
}
}
#[must_use]
pub fn on_navigate<F>(mut self, f: F) -> Self
where
F: Fn(String) -> Message + 'a,
{
self.on_navigate = Some(Box::new(f));
self
}
#[must_use]
pub fn on_back(mut self, message: Message) -> Self {
self.on_back = Some(message);
self
}
#[must_use]
pub fn on_forward(mut self, message: Message) -> Self {
self.on_forward = Some(message);
self
}
#[must_use]
pub fn on_reload(mut self, message: Message) -> Self {
self.on_reload = Some(message);
self
}
#[must_use]
pub fn on_stop(mut self, message: Message) -> Self {
self.on_stop = Some(message);
self
}
}
impl<'a, Message> From<BrowserBar<'a, Message>>
for iced::Element<'a, Message, iced::Theme>
where
Message: Clone + 'a,
{
fn from(bar: BrowserBar<'a, Message>) -> Self {
use iced::widget::{button, row, text, text_input};
use iced::Length;
let back_btn: iced::Element<'a, Message, iced::Theme> = {
let mut btn = button(text("←").size(16));
if bar.state.can_go_back {
if let Some(msg) = bar.on_back {
btn = btn.on_press(msg);
}
}
btn.into()
};
let forward_btn: iced::Element<'a, Message, iced::Theme> = {
let mut btn = button(text("→").size(16));
if bar.state.can_go_forward {
if let Some(msg) = bar.on_forward {
btn = btn.on_press(msg);
}
}
btn.into()
};
let reload_stop_btn: iced::Element<'a, Message, iced::Theme> = {
if bar.state.loading {
let mut btn = button(text("✕").size(16));
if let Some(msg) = bar.on_stop {
btn = btn.on_press(msg);
}
btn.into()
} else {
let mut btn = button(text("↻").size(16));
if let Some(msg) = bar.on_reload {
btn = btn.on_press(msg);
}
btn.into()
}
};
let url_input: iced::Element<'a, Message, iced::Theme> = {
let input = text_input("Enter URL...", &bar.state.url)
.width(Length::Fill);
if let Some(on_navigate) = bar.on_navigate {
input.on_submit(on_navigate(bar.state.url.clone())).into()
} else {
input.into()
}
};
row![back_btn, forward_btn, reload_stop_btn, url_input]
.spacing(4)
.padding(4)
.align_y(iced::Alignment::Center)
.into()
}
}