diff --git a/src/glyphs/obj.rs b/src/glyphs/obj.rs index 7ac8c19..ebad364 100644 --- a/src/glyphs/obj.rs +++ b/src/glyphs/obj.rs @@ -273,18 +273,54 @@ impl GlyphMetadata { Some("text"), clone!(@strong self as obj => move |entry, _| { let mut unicodes = obj.imp().unicode.borrow_mut(); + let mut kinds = obj.imp().kinds.borrow_mut(); let text = entry.text(); - if let Some(t) = text.strip_prefix("u+") { - unicodes.clear(); - unicodes.push(Unicode::new(t.to_string())); - } else if let Some(t) = text.strip_prefix("U+") { - unicodes.clear(); - unicodes.push(Unicode::new(t.to_string())); + if let Some(t) = text.strip_prefix("u+").or_else(|| text.strip_prefix("U+")) { + let val = Unicode::new(t.to_string()); + // TODO show error to user + if let Ok(kind) = GlyphKind::try_from(&val) { + kinds.0 = kind; + unicodes.clear(); + unicodes.push(val); + } } }), ); + let codepoint = + PropertyChoice::new("codepoint", gtk::RadioButton::new(), unicode_entry.upcast()); + let name_entry = gtk::Entry::builder() + .visible(true) + .expand(false) + .placeholder_text("component name") + .build(); + name_entry.buffer().connect_notify_local( + Some("text"), + clone!(@strong self as obj => move |entry, _| { + let mut unicodes = obj.imp().unicode.borrow_mut(); + unicodes.clear(); + let mut kinds = obj.imp().kinds.borrow_mut(); + let text = entry.text(); + kinds.0 = GlyphKind::from(text); + }), + ); + + let component = PropertyChoice::new( + "component", + gtk::RadioButton::from_widget(codepoint.button()), + name_entry.upcast(), + ); + let kind_box = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .spacing(5) + .expand(true) + .visible(true) + .can_focus(true) + .build(); + kind_box.pack_start(&codepoint, false, false, 5); + kind_box.pack_start(&component, false, false, 5); + kind_box.show_all(); w.add_separator(); - w.add("unicode", unicode_label, unicode_entry.upcast()); + w.add("unicode", unicode_label, kind_box.upcast()); } w } diff --git a/src/ufo/glif.rs b/src/ufo/glif.rs index 84be9d0..7ba020e 100644 --- a/src/ufo/glif.rs +++ b/src/ufo/glif.rs @@ -276,6 +276,45 @@ impl Unicode { pub fn new(hex: String) -> Self { Self { hex } } + + #[inline(always)] + pub fn hex(&self) -> &str { + self.hex.as_str() + } +} + +/// ``` +/// # use gerb::glyphs::GlyphKind; +/// # use gerb::ufo::glif::Unicode; +/// +/// assert_eq!(GlyphKind::try_from(&Unicode::new("0062".to_string())).unwrap(), GlyphKind::from('b')); +/// assert_eq!(GlyphKind::try_from(&Unicode::new("0041".to_string())).unwrap(), GlyphKind::from('A')); +/// assert_eq!(GlyphKind::try_from(&Unicode::new("00E6".to_string())).unwrap(), GlyphKind::from('æ')); +/// assert_eq!(GlyphKind::try_from(&Unicode::new("0021".to_string())).unwrap(), GlyphKind::from('!')); +/// ``` +impl TryFrom<&Unicode> for crate::glyphs::GlyphKind { + type Error = String; + + fn try_from(val: &Unicode) -> Result { + let num = u32::from_str_radix(val.hex(), 16) + .map_err(|err| format!("{} is not a valid hex value: {err}.", val.hex()))?; + Ok(crate::glyphs::GlyphKind::Char( + char::from_u32(num) + .ok_or_else(|| format!("{} = {num} is not a valid codepoint value.", val.hex()))?, + )) + } +} + +impl From for crate::glyphs::GlyphKind { + fn from(val: char) -> crate::glyphs::GlyphKind { + crate::glyphs::GlyphKind::Char(val) + } +} + +impl From for crate::glyphs::GlyphKind { + fn from(val: String) -> crate::glyphs::GlyphKind { + crate::glyphs::GlyphKind::Component(val) + } } #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Default)] diff --git a/src/utils.rs b/src/utils.rs index 002f9d7..2027de4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -236,3 +236,16 @@ macro_rules! impl_modified { } }; } + +#[macro_export] +macro_rules! impl_deref { + ($ty:ty, $inner:ty) => { + impl std::ops::Deref for $ty { + type Target = $inner; + + fn deref(&self) -> &Self::Target { + self.imp() + } + } + }; +} diff --git a/src/utils/property_window.rs b/src/utils/property_window.rs index 14c9cf8..82ed142 100644 --- a/src/utils/property_window.rs +++ b/src/utils/property_window.rs @@ -547,7 +547,7 @@ pub fn get_widget_for_value( .sensitive(readwrite) .visible(true) .halign(gtk::Align::Start) - .valign(gtk::Align::Start) + .valign(gtk::Align::Center) .use_alpha(true) .show_editor(true) .build(); @@ -828,3 +828,78 @@ pub fn new_property_window( w.set_child(Some(&scrolled_window)); w } + +#[derive(Default, Debug)] +pub struct PropertyChoiceInner { + pub btn: OnceCell, + pub widget: OnceCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for PropertyChoiceInner { + const NAME: &'static str = "PropertyChoice"; + type Type = PropertyChoice; + type ParentType = gtk::Box; +} + +impl ObjectImpl for PropertyChoiceInner { + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + obj.upcast_ref::() + .set_orientation(gtk::Orientation::Horizontal); + obj.set_spacing(1); + obj.set_expand(false); + obj.set_visible(true); + obj.set_can_focus(true); + } +} + +impl WidgetImpl for PropertyChoiceInner {} +impl ContainerImpl for PropertyChoiceInner {} +impl BoxImpl for PropertyChoiceInner {} + +glib::wrapper! { + pub struct PropertyChoice(ObjectSubclass) + @extends gtk::Widget, gtk::Container, gtk::Box; +} + +impl PropertyChoice { + pub fn new(label: &str, btn: gtk::RadioButton, widget: gtk::Widget) -> Self { + let ret: PropertyChoice = glib::Object::new(&[]).unwrap(); + let label = gtk::Label::builder() + .label(label) + .visible(true) + .selectable(false) + .max_width_chars(30) + .halign(gtk::Align::Start) + .wrap(true) + .expand(false) + .build(); + let event_box = gtk::EventBox::builder() + .events(gtk::gdk::EventMask::BUTTON_PRESS_MASK) + .above_child(true) + .child(&label) + .visible(true) + .build(); + ret.pack_start(&event_box, false, false, 5); + ret.pack_start(&btn, false, false, 5); + ret.pack_start(&widget, false, false, 5); + btn.bind_property("active", &widget, "sensitive") + .flags(glib::BindingFlags::SYNC_CREATE) + .build(); + event_box.connect_button_press_event(clone!(@weak btn => @default-return Inhibit(false), move |_, event| { + if event.button() == gtk::gdk::BUTTON_PRIMARY && event.event_type() == gtk::gdk::EventType::ButtonPress { + btn.set_active(true); + } + Inhibit(false) + })); + ret.btn.set(btn).unwrap(); + ret + } + + pub fn button(&self) -> >k::RadioButton { + self.btn.get().unwrap() + } +} + +impl_deref!(PropertyChoice, PropertyChoiceInner); diff --git a/src/views/collection.rs b/src/views/collection.rs index f9fc946..4758693 100644 --- a/src/views/collection.rs +++ b/src/views/collection.rs @@ -173,7 +173,6 @@ impl ObjectImpl for CollectionInner { save.connect_clicked(clone!(@weak metadata, @weak w, @weak obj => move |_| { let project = obj.project(); let name = metadata.name().to_string(); - //FIXME: set GlyphKind let glyph = Rc::new(RefCell::new(metadata.clone().into())); metadata.glyph_ref.set(glyph.clone()).unwrap(); //FIXME: show err msg