use std::{ops::Add, sync::Arc};
use bitflags::bitflags;
use iced::{
Element, Font,
widget::{self, text_editor},
};
use crate::state::MarkState;
#[derive(Debug, Default, Clone, Copy)]
pub struct ChildData {
pub heading_weight: u16,
pub flags: ChildDataFlags,
pub alignment: Option<ChildAlignment>,
pub li_ordered_number: Option<usize>,
}
impl ChildData {
pub fn heading(mut self, weight: u16) -> Self {
self.heading_weight = weight;
self
}
pub fn insert(mut self, flags: ChildDataFlags) -> Self {
self.flags.insert(flags);
self
}
pub fn ordered(mut self) -> Self {
self.li_ordered_number = Some(1);
self
}
}
#[derive(Debug, Clone, Copy)]
pub enum ChildAlignment {
Center,
Right,
}
impl From<ChildAlignment> for iced::Alignment {
fn from(val: ChildAlignment) -> Self {
match val {
ChildAlignment::Center => Self::Center,
ChildAlignment::Right => Self::End,
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, Default)]
pub struct ChildDataFlags: u16 {
const BOLD = 1 << 0;
const ITALIC = 1 << 1;
const UNDERLINE = 1 << 2;
const STRIKETHROUGH = 1 << 3;
const KEEP_WHITESPACE = 1 << 4;
const MONOSPACE = 1 << 5;
const SKIP_SUMMARY = 1 << 6;
const HIGHLIGHT = 1 << 7;
}
}
#[derive(Debug, Clone)]
pub struct UpdateMsg {
pub(crate) kind: UpdateMsgKind,
}
#[derive(Debug, Clone)]
pub enum UpdateMsgKind {
TextEditor(String, text_editor::Action),
DetailsToggle(usize, bool),
}
type FClickLink<M> = Box<dyn Fn(String) -> M>;
type FDrawImage<'a, M, T> = Box<dyn Fn(ImageInfo) -> Element<'static, M, T> + 'a>;
type FUpdate<M> = Arc<dyn Fn(UpdateMsg) -> M>;
pub(crate) type FStyleLinkButton<T> =
Arc<dyn Fn(&T, widget::button::Status) -> widget::button::Style + 'static>;
pub struct MarkWidget<'a, Message, Theme = iced::Theme> {
pub(crate) state: &'a MarkState,
pub(crate) font: Font,
pub(crate) font_mono: Font,
pub(crate) style: Option<crate::Style>,
pub(crate) text_size: f32,
pub(crate) heading_scale: f32,
pub(crate) fn_clicking_link: Option<FClickLink<Message>>,
pub(crate) fn_drawing_image: Option<FDrawImage<'a, Message, Theme>>,
pub(crate) fn_update: Option<FUpdate<Message>>,
pub(crate) fn_style_link_button: Option<FStyleLinkButton<Theme>>,
pub(crate) paragraph_spacing: Option<f32>,
pub(crate) current_dropdown_id: usize,
}
impl<'a, M: 'a, T: 'a> MarkWidget<'a, M, T> {
#[must_use]
pub fn new(state: &'a MarkState) -> Self {
Self {
state,
font: Font::DEFAULT,
font_mono: Font::MONOSPACE,
fn_clicking_link: None,
fn_drawing_image: None,
fn_update: None,
fn_style_link_button: None,
style: None,
current_dropdown_id: 0,
text_size: 16.0,
heading_scale: 1.0,
paragraph_spacing: None,
}
}
#[must_use]
pub fn font(mut self, font: Font) -> Self {
self.font = font;
self
}
#[must_use]
pub fn font_mono(mut self, font: Font) -> Self {
self.font_mono = font;
self
}
#[must_use]
pub fn text_size(mut self, size: impl Into<iced::Pixels>) -> Self {
self.text_size = size.into().0;
self
}
#[must_use]
pub fn heading_scale(mut self, scale: f32) -> Self {
assert!(scale >= 0.0);
self.heading_scale = scale;
self
}
#[must_use]
pub fn on_clicking_link(mut self, f: impl Fn(String) -> M + 'static) -> Self {
self.fn_clicking_link = Some(Box::new(f));
self
}
#[must_use]
pub fn on_drawing_image(
mut self,
f: impl Fn(ImageInfo) -> Element<'static, M, T> + 'a,
) -> Self {
self.fn_drawing_image = Some(Box::new(f));
self
}
#[must_use]
pub fn on_updating_state(mut self, f: impl Fn(UpdateMsg) -> M + 'static) -> Self {
self.fn_update = Some(Arc::new(f));
self
}
#[must_use]
pub fn style(mut self, style: crate::Style) -> Self {
self.style = Some(style);
self
}
#[must_use]
pub fn style_link_button(
mut self,
f: impl Fn(&T, widget::button::Status) -> widget::button::Style + 'static,
) -> Self {
self.fn_style_link_button = Some(Arc::new(f));
self
}
#[must_use]
pub fn paragraph_spacing(mut self, spacing: f32) -> Self {
self.paragraph_spacing = Some(spacing);
self
}
}
#[derive(Default)]
pub enum RenderedSpan<'a, M, T> {
Spans(Vec<widget::text::Span<'a, M, Font>>),
Elem(Element<'a, M, T>, Emp),
#[default]
None,
}
impl<M, T> std::fmt::Debug for RenderedSpan<'_, M, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RenderedSpan::Spans(spans) => {
write!(f, "Rs::Spans ")?;
f.debug_list()
.entries(spans.iter().map(|n| &*n.text))
.finish()
}
RenderedSpan::Elem(_, emp) => write!(f, "Rs::Elem({emp:?})"),
RenderedSpan::None => write!(f, "Rs::None"),
}
}
}
impl<'a, M, T> RenderedSpan<'a, M, T>
where
M: Clone + 'static,
T: widget::text::Catalog + 'a,
{
pub fn is_empty(&self) -> bool {
match self {
RenderedSpan::Spans(spans) => spans.is_empty(),
RenderedSpan::Elem(_, e) => matches!(e, Emp::Empty),
RenderedSpan::None => true,
}
}
pub fn render(self) -> Element<'a, M, T> {
match self {
RenderedSpan::Spans(spans) => widget::rich_text(spans).on_link_click(|n| n).into(),
RenderedSpan::Elem(element, _) => element,
RenderedSpan::None => widget::Column::new().into(),
}
}
}
impl<'a, M, T> Add for RenderedSpan<'a, M, T>
where
M: Clone + 'static,
T: widget::text::Catalog + 'a,
{
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
use RenderedSpan as Rs;
match (self, rhs) {
(Rs::None, rhs) => rhs,
(lhs, Rs::None) => lhs,
(Rs::Spans(mut spans1), Rs::Spans(spans2)) => {
spans1.extend(spans2);
Rs::Spans(spans1)
}
(r @ Rs::Spans(_), Rs::Elem(element, e)) => Rs::Elem(
widget::row![r.render()]
.push(e.has_something().then_some(element))
.spacing(5)
.wrap()
.into(),
Emp::NonEmpty,
),
(Rs::Elem(element, e), r @ Rs::Spans(_)) => Rs::Elem(
widget::Row::new()
.push(e.has_something().then_some(element))
.push(r.render())
.spacing(5)
.wrap()
.into(),
Emp::NonEmpty,
),
(Rs::Elem(e1, em1), Rs::Elem(e2, em2)) => Rs::Elem(
widget::Row::new()
.push(em1.has_something().then_some(e1))
.push(em2.has_something().then_some(e2))
.spacing(5)
.wrap()
.into(),
Emp::NonEmpty,
),
}
}
}
impl<'a, M, T, E> From<E> for RenderedSpan<'a, M, T>
where
M: Clone,
T: widget::text::Catalog + 'a,
E: Into<Element<'a, M, T>>,
{
fn from(value: E) -> Self {
Self::Elem(value.into(), Emp::NonEmpty)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Emp {
#[allow(unused)]
Empty,
NonEmpty,
}
impl Emp {
pub fn is_empty(self) -> bool {
match self {
Emp::Empty => true,
Emp::NonEmpty => false,
}
}
pub fn has_something(self) -> bool {
!self.is_empty()
}
}
#[non_exhaustive]
pub struct ImageInfo<'a> {
pub url: &'a str,
pub width: Option<f32>,
pub height: Option<f32>,
}