Skip to main content

cmdpal/
details.rs

1//! Types for displaying the details tab.
2//!
3//! `Details` is displayed in a tab occupying the right side of UI,
4//! displaying additional information about the selected item.
5
6use crate::icon::IconInfo;
7use crate::utils::{OkOrEmpty, assert_send_sync, map_array};
8use crate::{bindings::*, utils::ComBuilder};
9use windows_core::{AgileReference, HSTRING};
10use windows_core::{ComObject, Result, implement};
11
12/// Represents a tag for classification.
13///
14/// See: [`ITag`]
15///
16#[doc = include_str!("./bindings_docs/ITag.md")]
17#[implement(ITag)]
18pub struct Tag {
19    icon: Option<ComObject<IconInfo>>,
20    text: HSTRING,
21    foreground: Option<Color>,
22    background: Option<Color>,
23    tooltip: HSTRING,
24}
25
26/// Builder for [`Tag`].
27pub struct TagBuilder {
28    icon: Option<ComObject<IconInfo>>,
29    text: Option<HSTRING>,
30    foreground: Option<Color>,
31    background: Option<Color>,
32    tooltip: Option<HSTRING>,
33}
34
35impl TagBuilder {
36    /// Creates a builder.
37    pub fn new() -> Self {
38        TagBuilder {
39            icon: None,
40            text: None,
41            foreground: None,
42            background: None,
43            tooltip: None,
44        }
45    }
46
47    /// Sets the icon for the tag.
48    ///
49    #[doc = include_str!("./bindings_docs/ITag/Icon.md")]
50    pub fn icon(mut self, icon: ComObject<IconInfo>) -> Self {
51        self.icon = Some(icon);
52        self
53    }
54
55    /// Sets the text for the tag.
56    ///
57    #[doc = include_str!("./bindings_docs/ITag/Text.md")]
58    pub fn text(mut self, text: impl Into<HSTRING>) -> Self {
59        self.text = Some(text.into());
60        self
61    }
62
63    /// Sets the foreground color for the tag.
64    ///
65    #[doc = include_str!("./bindings_docs/ITag/Foreground.md")]
66    pub fn foreground(mut self, color: Color) -> Self {
67        self.foreground = Some(color);
68        self
69    }
70
71    /// Sets the background color for the tag.
72    ///
73    #[doc = include_str!("./bindings_docs/ITag/Background.md")]
74    pub fn background(mut self, color: Color) -> Self {
75        self.background = Some(color);
76        self
77    }
78
79    /// Sets the hover tooltip for the tag.
80    ///
81    #[doc = include_str!("./bindings_docs/ITag/ToolTip.md")]
82    pub fn tooltip(mut self, tooltip: impl Into<HSTRING>) -> Self {
83        self.tooltip = Some(tooltip.into());
84        self
85    }
86}
87
88impl ComBuilder for TagBuilder {
89    type Output = Tag;
90    fn build_unmanaged(self) -> Tag {
91        Tag {
92            icon: self.icon,
93            text: self.text.unwrap_or_else(|| HSTRING::new()),
94            foreground: self.foreground,
95            background: self.background,
96            tooltip: self.tooltip.unwrap_or_else(|| HSTRING::new()),
97        }
98    }
99}
100
101impl Default for TagBuilder {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107impl ITag_Impl for Tag_Impl {
108    fn Icon(&self) -> Result<crate::bindings::IIconInfo> {
109        self.icon
110            .as_ref()
111            .map(|icon| icon.to_interface())
112            .ok_or_empty()
113    }
114
115    fn Text(&self) -> Result<windows_core::HSTRING> {
116        Ok(self.text.clone())
117    }
118
119    fn Foreground(&self) -> Result<OptionalColor> {
120        Ok(self.foreground.into())
121    }
122
123    fn Background(&self) -> Result<OptionalColor> {
124        Ok(self.background.into())
125    }
126
127    fn ToolTip(&self) -> Result<windows_core::HSTRING> {
128        Ok(self.tooltip.clone())
129    }
130}
131
132/// Represents a collection of tags in details.
133///
134/// See: [`IDetailsTags`]
135///
136#[doc = include_str!("./bindings_docs/IDetailsTags.md")]
137#[implement(IDetailsTags, IDetailsData)]
138pub struct DetailsTags {
139    tags: Vec<ComObject<Tag>>,
140}
141
142/// Builder for [`DetailsTags`].
143pub struct DetailsTagsBuilder {
144    tags: Vec<ComObject<Tag>>,
145}
146
147impl DetailsTagsBuilder {
148    /// Creates a new builder.
149    pub fn new() -> Self {
150        DetailsTagsBuilder { tags: Vec::new() }
151    }
152
153    /// Adds a tag to the collection.
154    pub fn add_tag(mut self, tag: ComObject<Tag>) -> Self {
155        self.tags.push(tag);
156        self
157    }
158
159    /// Sets the tags for the collection.
160    pub fn tags(mut self, tags: Vec<ComObject<Tag>>) -> Self {
161        self.tags = tags;
162        self
163    }
164}
165
166impl ComBuilder for DetailsTagsBuilder {
167    type Output = DetailsTags;
168    fn build_unmanaged(self) -> DetailsTags {
169        DetailsTags { tags: self.tags }
170    }
171}
172
173impl Default for DetailsTagsBuilder {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179impl IDetailsData_Impl for DetailsTags_Impl {}
180
181impl IDetailsTags_Impl for DetailsTags_Impl {
182    fn Tags(&self) -> Result<windows_core::Array<ITag>> {
183        Ok(map_array(&self.tags, |x| x.to_interface::<ITag>().into()))
184    }
185}
186
187/// Represents a hyperlink.
188///
189/// See: [`IDetailsLink`]
190///
191#[doc = include_str!("./bindings_docs/IDetailsLink.md")]
192#[implement(IDetailsLink, IDetailsData)]
193pub struct DetailsLink {
194    text: HSTRING,
195    link: windows::Foundation::Uri,
196}
197
198/// Builder for [`DetailsLink`].
199pub struct DetailsLinkBuilder {
200    text: Option<HSTRING>,
201    link: windows::Foundation::Uri,
202}
203
204impl DetailsLinkBuilder {
205    /// Creates a new builder.
206    pub fn new(link: windows::Foundation::Uri) -> Self {
207        DetailsLinkBuilder { text: None, link }
208    }
209
210    /// Try to create a new builder from a string link.
211    pub fn try_new(link: impl Into<HSTRING>) -> Result<Self> {
212        let uri = windows::Foundation::Uri::CreateUri(&link.into())?;
213        Ok(DetailsLinkBuilder {
214            text: None,
215            link: uri,
216        })
217    }
218
219    /// Sets the display text for the link automatically based on the URI, if not already set.
220    pub fn auto_text(mut self) -> Result<Self> {
221        if self.text.is_none() {
222            self.text = Some(self.link.ToString()?);
223        }
224        Ok(self)
225    }
226
227    /// Sets the display text for the link.
228    pub fn text(mut self, text: impl Into<HSTRING>) -> Self {
229        self.text = Some(text.into());
230        self
231    }
232}
233
234impl ComBuilder for DetailsLinkBuilder {
235    type Output = DetailsLink;
236    fn build_unmanaged(self) -> DetailsLink {
237        DetailsLink {
238            text: self.text.unwrap_or_else(|| HSTRING::new()),
239            link: self.link,
240        }
241    }
242}
243
244impl IDetailsData_Impl for DetailsLink_Impl {}
245
246impl IDetailsLink_Impl for DetailsLink_Impl {
247    fn Text(&self) -> Result<windows_core::HSTRING> {
248        Ok(self.text.clone())
249    }
250
251    fn Link(&self) -> Result<windows::Foundation::Uri> {
252        Ok(self.link.clone())
253    }
254}
255
256/// Represents a command that can be executed from details tab.
257///
258/// See: [`IDetailsCommands`]
259///
260#[doc = include_str!("./bindings_docs/IDetailsCommands.md")]
261#[implement(IDetailsCommands, IDetailsData)]
262pub struct DetailsCommands {
263    commands: Vec<AgileReference<ICommand>>,
264}
265
266impl DetailsCommands {
267    /// Creates a new unmanaged instance of `DetailsCommand` with the specified commands.
268    pub fn try_new_unmanaged(commands: &[ICommand]) -> Result<Self> {
269        let agile_commands = commands
270            .iter()
271            .map(|cmd| AgileReference::new(cmd))
272            .collect::<Result<Vec<_>>>()?;
273        Ok(Self {
274            commands: agile_commands,
275        })
276    }
277
278    /// Creates a new reference-counted COM object for `DetailsCommand` with the specified commands.
279    pub fn try_new(commands: &[ICommand]) -> Result<ComObject<Self>> {
280        Ok(ComObject::new(Self::try_new_unmanaged(commands)?))
281    }
282
283    /// Creates a new unmanaged instance of `DetailsCommand` with the specified commands.
284    pub fn new_unmanaged(commands: &[AgileReference<ICommand>]) -> Self {
285        Self {
286            commands: Vec::from(commands),
287        }
288    }
289
290    /// Creates a new reference-counted COM object for `DetailsCommand` with the specified commands.
291    pub fn new(commands: &[AgileReference<ICommand>]) -> ComObject<Self> {
292        ComObject::new(Self::new_unmanaged(commands))
293    }
294}
295
296impl IDetailsData_Impl for DetailsCommands_Impl {}
297
298impl IDetailsCommands_Impl for DetailsCommands_Impl {
299    fn Commands(&self) -> windows_core::Result<windows_core::Array<ICommand>> {
300        Ok(map_array(&self.commands, |cmd| cmd.resolve().ok()))
301    }
302}
303
304/// Represents a separator in details tab.
305///
306/// See: [`IDetailsSeparator`]
307///
308/// Details contents lay out vertically,
309/// this separator could be used to visually separate different sections of details.
310///
311#[doc = include_str!("./bindings_docs/IDetailsSeparator.md")]
312#[implement(IDetailsSeparator, IDetailsData)]
313pub struct DetailsSeparator;
314
315impl DetailsSeparator {
316    pub fn new() -> ComObject<Self> {
317        ComObject::new(Self)
318    }
319}
320
321impl IDetailsData_Impl for DetailsSeparator_Impl {}
322
323impl IDetailsSeparator_Impl for DetailsSeparator_Impl {}
324
325/// Represents a collection of all possible detail data types.
326pub enum DetailsData {
327    Tags(ComObject<DetailsTags>),
328    Link(ComObject<DetailsLink>),
329    Commands(ComObject<DetailsCommands>),
330    Separator(ComObject<DetailsSeparator>),
331}
332
333impl From<&DetailsData> for IDetailsData {
334    fn from(data: &DetailsData) -> Self {
335        match data {
336            DetailsData::Tags(tags) => tags.to_interface(),
337            DetailsData::Link(link) => link.to_interface(),
338            DetailsData::Commands(commands) => commands.to_interface(),
339            DetailsData::Separator(separator) => separator.to_interface(),
340        }
341    }
342}
343
344/// Represents a detail metadata that can be used in [`Details`].
345///
346/// See: [`IDetailsElement`]
347///
348#[doc = include_str!("./bindings_docs/IDetailsElement.md")]
349#[implement(IDetailsElement)]
350pub struct DetailsElement {
351    key: HSTRING,
352    data: DetailsData,
353}
354
355impl DetailsElement {
356    /// Creates a new unmanaged instance of `DetailsElement` with the specified key and data.
357    pub fn new_unmanaged(key: impl Into<HSTRING>, data: DetailsData) -> Self {
358        DetailsElement {
359            key: key.into(),
360            data,
361        }
362    }
363
364    /// Creates a new reference-counted COM object for `DetailsElement` with the specified key and data.
365    pub fn new(key: impl Into<HSTRING>, data: DetailsData) -> ComObject<Self> {
366        Self::new_unmanaged(key, data).into()
367    }
368}
369
370impl IDetailsElement_Impl for DetailsElement_Impl {
371    fn Key(&self) -> Result<windows_core::HSTRING> {
372        Ok(self.key.clone())
373    }
374
375    fn Data(&self) -> Result<IDetailsData> {
376        Ok((&self.data).into())
377    }
378}
379
380/// Represents the details tab that can be displayed in a page.
381///
382/// See: [`IDetails`]
383///
384#[doc = include_str!("./bindings_docs/IDetails.md")]
385#[implement(IDetails)]
386pub struct Details {
387    hero_image: Option<ComObject<IconInfo>>,
388    title: HSTRING,
389    body: HSTRING,
390    metadata: Vec<ComObject<DetailsElement>>,
391}
392
393/// Builder for [`Details`].
394pub struct DetailsBuilder {
395    hero_image: Option<ComObject<IconInfo>>,
396    title: Option<HSTRING>,
397    body: Option<HSTRING>,
398    metadata: Vec<ComObject<DetailsElement>>,
399}
400
401impl DetailsBuilder {
402    /// Creates a new builder.
403    pub fn new() -> Self {
404        DetailsBuilder {
405            hero_image: None,
406            title: None,
407            body: None,
408            metadata: Vec::new(),
409        }
410    }
411
412    /// Sets the hero image for the details.
413    pub fn hero_image(mut self, hero_image: ComObject<IconInfo>) -> Self {
414        self.hero_image = Some(hero_image);
415        self
416    }
417
418    /// Sets the title for the details.
419    pub fn title(mut self, title: impl Into<HSTRING>) -> Self {
420        self.title = Some(title.into());
421        self
422    }
423
424    /// Sets the body text for the details.
425    pub fn body(mut self, body: impl Into<HSTRING>) -> Self {
426        self.body = Some(body.into());
427        self
428    }
429
430    /// Sets the metadata (elements) for the details.
431    pub fn metadata(mut self, metadata: Vec<ComObject<DetailsElement>>) -> Self {
432        self.metadata = metadata;
433        self
434    }
435
436    /// Adds a metadata (element) to the details.
437    pub fn add_metadata(mut self, element: ComObject<DetailsElement>) -> Self {
438        self.metadata.push(element);
439        self
440    }
441
442    /// Adds a metadata (element) without a key to the details.
443    pub fn add_unnamed_metadata(mut self, data: DetailsData) -> Self {
444        let element = DetailsElement::new_unmanaged(HSTRING::new(), data);
445        self.metadata.push(ComObject::new(element));
446        self
447    }
448}
449
450impl ComBuilder for DetailsBuilder {
451    type Output = Details;
452    fn build_unmanaged(self) -> Details {
453        Details {
454            hero_image: self.hero_image,
455            title: self.title.unwrap_or_else(|| HSTRING::new()),
456            body: self.body.unwrap_or_else(|| HSTRING::new()),
457            metadata: self.metadata,
458        }
459    }
460}
461
462impl Default for DetailsBuilder {
463    fn default() -> Self {
464        Self::new()
465    }
466}
467
468impl IDetails_Impl for Details_Impl {
469    fn HeroImage(&self) -> Result<crate::bindings::IIconInfo> {
470        self.hero_image
471            .as_ref()
472            .map(|icon| icon.to_interface())
473            .ok_or_empty()
474    }
475
476    fn Title(&self) -> Result<windows_core::HSTRING> {
477        Ok(self.title.clone())
478    }
479
480    fn Body(&self) -> Result<windows_core::HSTRING> {
481        Ok(self.body.clone())
482    }
483
484    fn Metadata(&self) -> Result<windows_core::Array<IDetailsElement>> {
485        Ok(map_array(&self.metadata, |x| {
486            x.to_interface::<IDetailsElement>().into()
487        }))
488    }
489}
490
491const _: () = assert_send_sync::<DetailsData>();
492const _: () = assert_send_sync::<ComObject<Details>>();