glade/
lib.rs

1// SPDX-FileCopyrightText: 2021 Agathe Porte <microjoe@microjoe.org>
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Easily import Glade-generated UI files into Rust code.
6//!
7//! ```
8//! use gtk::prelude::*;
9//! use gladis::Gladis;
10//!
11//! const GLADE_SRC: &str = r#"
12//! <?xml version="1.0" encoding="UTF-8"?>
13//! <!-- Generated with glade 3.22.2 -->
14//! <interface>
15//!   <requires lib="gtk+" version="3.20"/>
16//!   <object class="GtkApplicationWindow" id="window">
17//!     <property name="can_focus">False</property>
18//!     <child type="titlebar">
19//!       <placeholder/>
20//!     </child>
21//!     <child>
22//!       <object class="GtkLabel" id="label">
23//!         <property name="visible">True</property>
24//!         <property name="can_focus">False</property>
25//!         <property name="label" translatable="yes">label</property>
26//!       </object>
27//!     </child>
28//!   </object>
29//! </interface>"#;
30//!
31//! #[derive(Gladis, Clone)]
32//! pub struct Window {
33//!     pub window: gtk::ApplicationWindow,
34//!     pub label: gtk::Label,
35//! }
36//!
37//! gtk::init().unwrap();
38//! let _ui = Window::from_string(GLADE_SRC).unwrap();
39//! ```
40
41use std::{error::Error, fmt::Display};
42
43#[derive(Debug, Clone)]
44pub struct NotFoundError {
45    pub identifier: String,
46    pub typ: String,
47}
48
49impl Display for NotFoundError {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(
52            f,
53            "identifier {} of type {} was not found",
54            self.identifier, self.typ
55        )
56    }
57}
58
59impl Error for NotFoundError {}
60
61#[derive(Debug, Clone)]
62pub enum GladisError {
63    NotFound(NotFoundError),
64}
65
66impl Display for GladisError {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        match self {
69            GladisError::NotFound(e) => write!(f, "not found error: {}", e),
70        }
71    }
72}
73
74impl Error for GladisError {
75    fn source(&self) -> Option<&(dyn Error + 'static)> {
76        match self {
77            GladisError::NotFound(e) => Some(e),
78        }
79    }
80}
81
82impl GladisError {
83    pub fn not_found<T>(identifier: T, typ: T) -> Self
84    where
85        T: ToString,
86    {
87        let identifier = identifier.to_string();
88        let typ = typ.to_string();
89        GladisError::NotFound(NotFoundError { identifier, typ })
90    }
91}
92
93pub type Result<T> = std::result::Result<T, GladisError>;
94
95pub trait Gladis {
96    //! A trait to load a struct from a builder.
97    //!
98    //! # Automatic implementation
99    //!
100    //! This trait wakes little sense alone, but truly show its power when used
101    //! with the [glade_derive](https://docs.rs/gladis_proc_macro) crate
102    //! and its `#[derive(Gladis)]` macro.
103    //!
104    //! ```
105    //! use gtk::prelude::*;
106    //! use gladis::Gladis;
107    //!
108    //! #[derive(Gladis, Clone)]
109    //! pub struct Window {
110    //!     pub window: gtk::ApplicationWindow,
111    //!     pub label: gtk::Label,
112    //! }
113    //! ```
114    //!
115    //! # Manual implementation
116    //!
117    //! Below is an example of manual implementation of the trait.
118    //!
119    //! ```
120    //! use gtk::prelude::*;
121    //! use gladis::{Gladis, Result, GladisError};
122    //!
123    //! pub struct Window {
124    //!     pub window: gtk::ApplicationWindow,
125    //!     pub label: gtk::Label,
126    //! }
127    //!
128    //! impl Gladis for Window {
129    //!     fn from_builder(builder: gtk::Builder) -> Result<Self> {
130    //!         let window: gtk::ApplicationWindow = builder
131    //!             .object("window")
132    //!             .ok_or(GladisError::not_found("window", "gtk::ApplicationWindow"))?;
133    //!
134    //!         let label: gtk::Label = builder
135    //!             .object("label")
136    //!             .ok_or(GladisError::not_found("label", "gtk::Label"))?;
137    //!
138    //!         Ok(Self { window, label })
139    //!     }
140    //! }
141    //! ```
142
143    /// Populate struct from a builder.
144    ///
145    /// This method should not be called directly but is used as a common
146    /// function for the `from_string` and `from_resource` functions to
147    /// share the same code.
148    fn from_builder(builder: gtk::Builder) -> Result<Self>
149    where
150        Self: std::marker::Sized;
151
152    /// Populate struct from a Glade document.
153    fn from_string(src: &str) -> Result<Self>
154    where
155        Self: std::marker::Sized,
156    {
157        let builder = gtk::Builder::from_string(src);
158        Gladis::from_builder(builder)
159    }
160
161    /// Populate struct from a Glade document as a resource.
162    fn from_resource(resource_path: &str) -> Result<Self>
163    where
164        Self: std::marker::Sized,
165    {
166        let builder = gtk::Builder::from_resource(resource_path);
167        Gladis::from_builder(builder)
168    }
169}
170
171// Re-export #[derive(Gladis)].
172#[cfg(feature = "derive")]
173#[doc(hidden)]
174pub use glade_derive::Gladis;
175
176#[cfg(test)]
177mod tests {
178    use crate::{GladisError, NotFoundError};
179
180    #[test]
181    fn fmt_not_found_error() {
182        let err = NotFoundError {
183            identifier: "foo".to_string(),
184            typ: "bar".to_string(),
185        };
186        assert_eq!(err.to_string(), "identifier foo of type bar was not found");
187    }
188
189    #[test]
190    fn fmt_gladis_error() {
191        let err = GladisError::NotFound(NotFoundError {
192            identifier: "foo".to_string(),
193            typ: "bar".to_string(),
194        });
195        assert_eq!(
196            err.to_string(),
197            "not found error: identifier foo of type bar was not found"
198        );
199    }
200}