use core::ptr::NonNull;
use std::ffi::{CStr, CString, c_void};
use crate::ffi::{
noesis_base_component_release, noesis_text_inlines_bold_create,
noesis_text_inlines_collection_add, noesis_text_inlines_collection_clear,
noesis_text_inlines_collection_count, noesis_text_inlines_collection_get,
noesis_text_inlines_hyperlink_create, noesis_text_inlines_hyperlink_get_navigate_uri,
noesis_text_inlines_hyperlink_set_navigate_uri,
noesis_text_inlines_inline_get_text_decorations,
noesis_text_inlines_inline_set_text_decorations, noesis_text_inlines_italic_create,
noesis_text_inlines_line_break_create, noesis_text_inlines_run_create,
noesis_text_inlines_run_get_text, noesis_text_inlines_run_set_text,
noesis_text_inlines_span_create, noesis_text_inlines_span_get_inlines,
noesis_text_inlines_text_block_get_inlines, noesis_text_inlines_ui_container_create,
noesis_text_inlines_ui_container_get_child, noesis_text_inlines_ui_container_set_child,
noesis_text_inlines_underline_create,
};
use crate::view::FrameworkElement;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
#[non_exhaustive]
pub enum TextDecorations {
None = 0,
OverLine = 1,
Baseline = 2,
Underline = 3,
Strikethrough = 4,
}
impl TextDecorations {
fn from_raw(v: i32) -> Option<Self> {
match v {
0 => Some(Self::None),
1 => Some(Self::OverLine),
2 => Some(Self::Baseline),
3 => Some(Self::Underline),
4 => Some(Self::Strikethrough),
_ => None,
}
}
}
pub trait Inline {
fn inline_raw(&self) -> *mut c_void;
fn set_text_decorations(&self, decorations: TextDecorations) -> bool {
unsafe {
noesis_text_inlines_inline_set_text_decorations(self.inline_raw(), decorations as i32)
}
}
fn text_decorations(&self) -> Option<TextDecorations> {
let v = unsafe { noesis_text_inlines_inline_get_text_decorations(self.inline_raw()) };
TextDecorations::from_raw(v)
}
}
macro_rules! inline_handle {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
pub struct $name {
ptr: NonNull<c_void>,
}
unsafe impl Send for $name {}
impl $name {
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Inline for $name {
fn inline_raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for $name {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
};
}
inline_handle!(
Run
);
inline_handle!(
Span
);
inline_handle!(
Bold
);
inline_handle!(
Italic
);
inline_handle!(
Underline
);
inline_handle!(
Hyperlink
);
inline_handle!(
LineBreak
);
inline_handle!(
InlineUIContainer
);
fn new_handle(ptr: *mut c_void, what: &str) -> NonNull<c_void> {
NonNull::new(ptr).unwrap_or_else(|| panic!("{what} returned null"))
}
impl Run {
#[must_use]
pub fn new(text: &str) -> Self {
let c = CString::new(text).expect("run text contained interior NUL");
let ptr = unsafe { noesis_text_inlines_run_create(c.as_ptr()) };
Self {
ptr: new_handle(ptr, "noesis_text_inlines_run_create"),
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_text(&mut self, text: &str) -> bool {
let c = CString::new(text).expect("run text contained interior NUL");
unsafe { noesis_text_inlines_run_set_text(self.ptr.as_ptr(), c.as_ptr()) }
}
#[must_use]
pub fn text(&self) -> Option<String> {
let p = unsafe { noesis_text_inlines_run_get_text(self.ptr.as_ptr()) };
if p.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
}
}
impl Span {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { noesis_text_inlines_span_create() };
Self {
ptr: new_handle(ptr, "noesis_text_inlines_span_create"),
}
}
#[must_use]
pub fn inlines(&self) -> Option<InlineCollection> {
let ptr = unsafe { noesis_text_inlines_span_get_inlines(self.ptr.as_ptr()) };
InlineCollection::from_raw(ptr)
}
}
impl Default for Span {
fn default() -> Self {
Self::new()
}
}
macro_rules! span_subclass {
($name:ident, $create:ident, $what:literal) => {
impl $name {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { $create() };
Self {
ptr: new_handle(ptr, $what),
}
}
#[must_use]
pub fn inlines(&self) -> Option<InlineCollection> {
let ptr = unsafe { noesis_text_inlines_span_get_inlines(self.ptr.as_ptr()) };
InlineCollection::from_raw(ptr)
}
}
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
};
}
span_subclass!(
Bold,
noesis_text_inlines_bold_create,
"noesis_text_inlines_bold_create"
);
span_subclass!(
Italic,
noesis_text_inlines_italic_create,
"noesis_text_inlines_italic_create"
);
span_subclass!(
Underline,
noesis_text_inlines_underline_create,
"noesis_text_inlines_underline_create"
);
span_subclass!(
Hyperlink,
noesis_text_inlines_hyperlink_create,
"noesis_text_inlines_hyperlink_create"
);
impl Hyperlink {
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_navigate_uri(&mut self, uri: &str) -> bool {
let c = CString::new(uri).expect("navigate uri contained interior NUL");
unsafe { noesis_text_inlines_hyperlink_set_navigate_uri(self.ptr.as_ptr(), c.as_ptr()) }
}
#[must_use]
pub fn navigate_uri(&self) -> Option<String> {
let p = unsafe { noesis_text_inlines_hyperlink_get_navigate_uri(self.ptr.as_ptr()) };
if p.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned();
if s.is_empty() { None } else { Some(s) }
}
}
impl LineBreak {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { noesis_text_inlines_line_break_create() };
Self {
ptr: new_handle(ptr, "noesis_text_inlines_line_break_create"),
}
}
}
impl Default for LineBreak {
fn default() -> Self {
Self::new()
}
}
impl InlineUIContainer {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { noesis_text_inlines_ui_container_create() };
Self {
ptr: new_handle(ptr, "noesis_text_inlines_ui_container_create"),
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_child(&mut self, child: &FrameworkElement) -> bool {
unsafe { noesis_text_inlines_ui_container_set_child(self.ptr.as_ptr(), child.raw()) }
}
#[must_use]
pub fn child_raw(&self) -> *mut c_void {
unsafe { noesis_text_inlines_ui_container_get_child(self.ptr.as_ptr()) }
}
#[must_use]
pub fn has_child(&self) -> bool {
!self.child_raw().is_null()
}
}
impl Default for InlineUIContainer {
fn default() -> Self {
Self::new()
}
}
pub struct InlineCollection {
ptr: NonNull<c_void>,
}
unsafe impl Send for InlineCollection {}
impl InlineCollection {
fn from_raw(ptr: *mut c_void) -> Option<Self> {
NonNull::new(ptr).map(|ptr| Self { ptr })
}
pub fn add<I: Inline>(&mut self, inline: &I) -> Option<usize> {
let idx =
unsafe { noesis_text_inlines_collection_add(self.ptr.as_ptr(), inline.inline_raw()) };
(idx >= 0).then_some(idx as usize)
}
#[must_use]
pub fn count(&self) -> usize {
let n = unsafe { noesis_text_inlines_collection_count(self.ptr.as_ptr()) };
n.max(0) as usize
}
#[must_use]
pub fn get_raw(&self, index: usize) -> *mut c_void {
unsafe { noesis_text_inlines_collection_get(self.ptr.as_ptr(), index as u32) }
}
pub fn clear(&mut self) {
unsafe { noesis_text_inlines_collection_clear(self.ptr.as_ptr()) }
}
}
impl Drop for InlineCollection {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
#[must_use]
pub fn text_block_inlines(element: &FrameworkElement) -> Option<InlineCollection> {
let ptr = unsafe { noesis_text_inlines_text_block_get_inlines(element.raw()) };
InlineCollection::from_raw(ptr)
}