use core::error::Error;
#[cfg(feature = "snackbar")]
use waterui_core::State;
use waterui_core::View;
use waterui_graphics::color::Color;
use waterui_layout::{
spacer,
stack::{HorizontalAlignment, VStack, hstack},
};
use waterui_str::Str;
use waterui_text::{
font::{Body, Font},
highlight::{DefaultHighlighter, Highlighter, Language},
styled::{Style, StyledStr},
text,
};
#[cfg(target_arch = "wasm32")]
use executor_core::spawn_local;
use crate::ViewExt;
#[cfg(feature = "snackbar")]
use crate::snackbar::{Snackbar, SnackbarManager};
#[cfg(all(
not(target_os = "android"),
not(target_arch = "wasm32"),
not(target_os = "espidf")
))]
fn copy_to_clipboard(text: &str) {
match arboard::Clipboard::new() {
Ok(mut clipboard) => {
if let Err(error) = clipboard.set_text(text) {
tracing::error!(%error, "Failed to copy to clipboard");
}
}
Err(error) => {
tracing::error!(%error, "Failed to access clipboard");
}
}
}
#[cfg(target_os = "espidf")]
fn copy_to_clipboard(_text: &str) {}
#[cfg(target_os = "android")]
fn copy_to_clipboard(text: &str) {
if let Err(error) = android_clipboard::set_text(text.to_string()) {
tracing::error!(%error, "Failed to copy to clipboard");
}
}
#[cfg(target_arch = "wasm32")]
fn copy_to_clipboard(text: &str) {
let clipboard = web_sys::window()
.expect("browser window is unavailable for clipboard access")
.navigator()
.clipboard();
let text = text.to_string();
spawn_local(async move {
let promise = clipboard.write_text(&text);
if let Err(error) = wasm_bindgen_futures::JsFuture::from(promise).await {
tracing::error!(?error, "Failed to write browser clipboard text");
}
})
.detach();
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Code {
language: Language,
content: Str,
}
impl Code {
pub fn new(language: impl TryInto<Language, Error: Error>, content: impl Into<Str>) -> Self {
Self {
language: language.try_into().expect("Invalid language"),
content: content.into(),
}
}
}
impl View for Code {
fn body(self, _env: &waterui_core::Environment) -> impl View {
let lang_name = self.language.to_string();
let content_for_copy = self.content.to_string();
let mut highlighter = DefaultHighlighter::new();
let chunks = highlighter.highlight(self.language, &self.content);
let code_font = Font::from(Body).size(14.0);
let styled = chunks.into_iter().fold(StyledStr::empty(), |mut s, chunk| {
s.push(
chunk.text.to_string(),
Style::default()
.foreground(chunk.color)
.font(code_font.clone()),
);
s
});
VStack::new(
HorizontalAlignment::Leading,
8.0,
(
hstack((
text(lang_name)
.bold()
.foreground(Color::srgb_f32(0.85, 0.86, 0.9)),
spacer(),
copy_button(content_for_copy),
)),
text(styled),
),
)
.padding()
.background(Color::srgb_f32(0.15, 0.15, 0.18))
}
}
#[cfg(feature = "snackbar")]
fn copy_button(content: String) -> impl View {
text("Copy")
.foreground(Color::srgb_f32(0.72, 0.74, 0.8))
.on_tap(move |State(snackbar): State<SnackbarManager>| {
copy_to_clipboard(&content);
snackbar.show(Snackbar::new("Copied to clipboard"));
})
}
#[cfg(not(feature = "snackbar"))]
fn copy_button(content: String) -> impl View {
text("Copy")
.foreground(Color::srgb_f32(0.72, 0.74, 0.8))
.on_tap(move || copy_to_clipboard(&content))
}
pub fn code(language: impl TryInto<Language, Error: Error>, content: impl Into<Str>) -> Code {
Code::new(language, content)
}