User avatar
rmcq
Topic Author
Posts: 16
Joined: 18 Feb 2020, 23:31

Parse Text - Render Controller Button Glyphs

24 Mar 2020, 03:27

Hi team,

We’re having a problem replicating in Noesis a feature we had in our old Font Renderer.

In our old system, we could parse a piece of text. Using certain code, we could display a glyph of a controller button (like an emoji). These glyphs would be part of our fonts. The glyphs would change depending on what platform you were playing the game on. Text example:
Some text here. Here is a controller button: |CROSS|. And another: |SQUARE|

What would be the best way to replicate this in Noesis?

Thanks,

R
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: Parse Text - Render Controller Button Glyphs

25 Mar 2020, 11:51

Are those controller button glyphs from a specific font or images?

You can use TextBlock inlines to build a text containing images like this:

 
User avatar
jsantos
Site Admin
Posts: 3905
Joined: 20 Jan 2012, 17:18
Contact:

Re: Parse Text - Render Controller Button Glyphs

25 Mar 2020, 13:20

If they are glyphs you can also use the fontfallback mechanism, for example:
TextBlock FontFamily="Fonts/#Tahona, Fonts/#GamepadButtons" />
 
User avatar
rmcq
Topic Author
Posts: 16
Joined: 18 Feb 2020, 23:31

Re: Parse Text - Render Controller Button Glyphs

08 Apr 2020, 09:49

Hey team,

We've previously had them as glyphs in the font. However, I'm trying to implement this using sfernandez's approach with inline images. We have them stored in a control template at the moment, so I've written some code to put them in a Content Control.

I was able to get it parsing the text and displaying the image correctly in Blend using C# with the following code:
namespace Extensions
{
	// Source: https://stackoverflow.com/questions/14058814/format-part-of-the-text-of-textblock-using-ivalueconverter
	public class paTextBlockTooltipEx
	{
		public static Inline GetFormattedText(DependencyObject obj)
		{
			return (Inline)obj.GetValue(FormattedTextProperty);
		}

		public static void SetFormattedText(DependencyObject obj, Inline value)
		{
			obj.SetValue(FormattedTextProperty, value);
		}

		public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
			"FormattedText",
			typeof(Inline),
			typeof(paTextBlockTooltipEx),
			new PropertyMetadata(null, OnFormattedTextChanged)
		);

		private static void OnFormattedTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			if (!(o is TextBlock textBlock))
			{
				return;
			}

			InlineCollection inlines = textBlock.Inlines;
			inlines.Clear();
			Inline newInlineValue = (Inline)e.NewValue;
			if (newInlineValue != null)
			{
				inlines.Add(newInlineValue);
			}
		}
	}

	public class InlineInputTypeConverter : IValueConverter
	{
		private static readonly Dictionary<string, TooltipsVM.InputType> SymbolsPaths = new Dictionary<string, TooltipsVM.InputType> {
			{ "CROSS", TooltipsVM.InputType.Cross},
		};

		public TooltipConverter TooltipConverterProp { get; private set; }

		public InlineInputTypeConverter()
		{
			TooltipConverterProp = new TooltipConverter();
		}

		public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
		{
			string valueStr = value != null ? (string)value : (string)parameter;
			string[] splitStrings = valueStr.Split('|');
			Span textBlock = new Span();
			InlineCollection inlines = textBlock.Inlines;
			foreach(string splitStr in splitStrings)
			{
				if (SymbolsPaths.ContainsKey(splitStr))
				{
					var content = new ContentControl();
					content.Template = (ControlTemplate)TooltipConverterProp.ConvertInputType(SymbolsPaths[splitStr]);
					var viewbox = new Viewbox();
					viewbox.Child = content;
					viewbox.MaxHeight = 60;
					inlines.Add(new InlineUIContainer(viewbox));
				}
				else
				{
					inlines.Add(new Run(splitStr));
				}

			}
			return textBlock;
		}
	}
}
	<TextBlock 
		Foreground="Black"
		paEx:paTextBlockTooltipEx.FormattedText="{Binding ConverterParameter='Cross = |CROSS|',
		Converter={StaticResource inlineInputTypeConverter}}"
	/>
However, I'm struggling to convert this to C++. I don't know what paTextBlockTooltipEx's base class should be. I thought it may need need to be a Noesis::MarkupExtension but I couldn't find any sample code to point me in the right direction.

This is what I have so far:
class paTextBlockTooltipEx : public Noesis::MarkupExtension
{
	public:
		static const Noesis::Inline& GetFormattedText(const Noesis::DependencyObject& obj);
		static void SetFormattedText(Noesis::DependencyObject* obj, const Noesis::Inline& value);
		static const Noesis::DependencyProperty* m_FormattedTextProperty;

		virtual Noesis::Ptr<Noesis::BaseComponent> ProvideValue(const Noesis::ValueTargetProvider* provider) override { return nullptr; };
		NS_DECLARE_REFLECTION(paTextBlockTooltipEx, Noesis::MarkupExtension)
		LUA_BIND_CLASS_HEADER();

	private:
		static void OnFormattedTextChanged(Noesis::DependencyObject*, const Noesis::DependencyPropertyChangedEventArgs& e);
};
const Noesis::Inline& paTextBlockTooltipEx::GetFormattedText(const Noesis::DependencyObject& obj)
{
	return obj.GetValue<Noesis::Inline>(m_FormattedTextProperty);
}

void paTextBlockTooltipEx::SetFormattedText(Noesis::DependencyObject* obj, const Noesis::Inline& value)
{
	obj->SetValue<Noesis::Inline>(m_FormattedTextProperty, value);
}

void paTextBlockTooltipEx::OnFormattedTextChanged(Noesis::DependencyObject* obj, const Noesis::DependencyPropertyChangedEventArgs& e)
{
	auto textBlock = (Noesis::TextBlock*)obj;
	if (!textBlock)
	{
		return;
	}

	auto inlines = textBlock->GetInlines();
	if (!inlines)
	{
		return;
	}

	inlines->Clear();
	const auto newInlineValue = (Noesis::Inline*)e.newValue;
	if (!newInlineValue)
	{
		return;
	}

	inlines->Add(newInlineValue);
}

NS_BEGIN_COLD_REGION
NS_IMPLEMENT_REFLECTION(paTextBlockTooltipEx)
{
	NsMeta("FormattedText", &paTextBlockTooltipEx::m_FormattedTextProperty);
}
Any ideas?

Thanks,
R
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: Parse Text - Render Controller Button Glyphs

08 Apr 2020, 11:35

It should be similar to Blend, the class just needs to define the FormattedText dependency property. And looking at your code I think you can simplify it, avoiding the converter part and just writing that conversion inside the attached property changed handler:
struct paTextBlockTooltipEx
{
  static const DependencyProperty* FormattedTextProperty;

  static void OnFormattedTextChanged(DependencyObject* d, const DependencyPropertyChangedEventArgs& e)
  {
    TextBlock* textBlock = DynamicCast<TextBlock*>(d);
    if (textBlock != nullptr)
    {
      InlineCollection* inlines = textBlock->GetInlines();
      inlines->Clear();

      const String& newValue = *static_cast<const String*>(e.newValue);
      // convert value string tokens to the appropriate inline
      // and directly add them to the TextBlock.Inlines collection
      // ...
    }
  }

  NS_IMPLEMENT_INLINE_REFLECTION(paTextBlockTooltipEx, NoParent, "paEx.paTextBlockTooltipEx")
  {
    DependencyData* data = NsMeta<DependencyData>(TypeOf<paTextBlockTooltipEx>());
    data->RegisterProperty<String>(FormattedTextProperty, "FormattedText",
      PropertyMetadata::Create(String(), OnFormattedTextChanged));
  }
};
<TextBlock paEx:paTextBlockTooltipEx.FormattedText="{Binding}"/>
 
User avatar
rmcq
Topic Author
Posts: 16
Joined: 18 Feb 2020, 23:31

Re: Parse Text - Render Controller Button Glyphs

09 Apr 2020, 04:31

It says NS_IMPLEMENT_INLINE_REFLECTION only takes two arguments: classType and parentType.
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: Parse Text - Render Controller Button Glyphs

09 Apr 2020, 10:19

Sorry, that code is how it would look in NoesisGUI 3.0, we did a few changes to reflection macros to simplify them.
The third parameter (the type name), corresponds to NsMeta<TypeId> in 2.2 version:
  NS_IMPLEMENT_INLINE_REFLECTION(paTextBlockTooltipEx, NoParent)
  {
    NsMeta<TypeId>("paEx.paTextBlockTooltipEx");
    DependencyData* data = NsMeta<DependencyData>(TypeOf<paTextBlockTooltipEx>());
    data->RegisterProperty<String>(FormattedTextProperty, "FormattedText",
      PropertyMetadata::Create(String(), OnFormattedTextChanged));
  }
 
User avatar
rmcq
Topic Author
Posts: 16
Joined: 18 Feb 2020, 23:31

Re: Parse Text - Render Controller Button Glyphs

14 Apr 2020, 05:52

I'm still not getting anything to show up in the C++. I must be missing something here.

I'm getting these errors in the output:
ERROR: noesis/Workspace/Views/InlineInputTypeTest.xaml(25): Unknown type 'paNoesisExtensions.paTextBlockTooltipEx'.
So I tried changing it to:
NsMeta<TypeId>("paNoesisExtensions.paTextBlockTooltipEx");
But that doesn't change anything. I still don't see anything and I still get the errors.

I've been reading through this because I think it's the relevant documentation:
https://www.noesisengine.com/docs/Gui.D ... Index.html

Questions I have:
- Does my class have to derive from Dependency Object to use a Dependency Property? I ask because of this line here from the documentation:
Your attached property can then be set on any class that derives from DependencyObject.
- Do I still need Getters and Setters for my property? Your simplification got rid of them.
- Do I have to construct and/or register the class anywhere? I presume not because it uses a static.
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: Parse Text - Render Controller Button Glyphs

14 Apr 2020, 10:39

Hi,
So I tried changing it to: NsMeta<TypeId>("paNoesisExtensions.paTextBlockTooltipEx");
That is correct, the TypeId must be the same you use in xaml.
But that doesn't change anything. I still don't see anything and I still get the errors.
I think you just missed to register the extension class in the Reflection. You can force that by calling TypeOf<T>() when you register the rest of components of your application in the component Factory:
void RegisterComponents()
{
  TypeOf<paTextBlockTooltipEx>();
  NsRegisterComponent<SomeUserControl>();
  NsRegisterComponent<OtherControl>();
  ...
}
The xaml parser uses the information stored in the Reflection to find available classes and properties.
Does my class have to derive from Dependency Object to use a Dependency Property?
If your class only defines attached properties it does not have to inherit from DependencyObject, because those properties won't be set in the class itself, but on other objects.
Do I still need Getters and Setters for my property?
Getters and Setters are not required, but you could add them for convenience if you plan to modify the property in code.
Do I have to construct and/or register the class anywhere?
In this case the class (just defines an attached property) does not need to be created anywhere, but as I said before, in order to be available to the xaml parser its reflection must be created and registered. When calling the template function TypeOf it automatically creates the corresponding reflection type and registers it in our Reflection Registry.

Hope this helps.
 
User avatar
rmcq
Topic Author
Posts: 16
Joined: 18 Feb 2020, 23:31

Re: Parse Text - Render Controller Button Glyphs

21 Apr 2020, 04:15

You were correct, I had to register it. It's working now. Thank you very much for your help. :)

Do you know of any good documentation for general WPF Extension Classes like this? I fumbled my way through this one but it would be good to read up and have a better idea what I'm doing next time.

Who is online

Users browsing this forum: Google [Bot] and 35 guests