├── .github
└── FUNDING.yml
├── LICENSE
├── README.md
├── comparisons
├── maui-default-page.md
├── maui-sample-page.md
├── readme.md
├── winui-new-window.md
└── wpf-new-window.md
├── enamel-tease.gif
└── slides
├── 00_the-basics.png
├── 01_root-elements.png
├── 02_optional-braces.png
├── 03_properties.png
├── 04_properties2.png
├── 05_default-properties.png
├── 06_multi-properties.png
├── 07_default-markuExtension.png
├── 08_comments.png
├── 09_substitutions.png
├── 10_expansions-dotattributes.png
├── 11_for-loops.png
├── 12_foreach-loops.png
├── 13_autogrids.png
├── 14_inlinecsharp.png
└── readme.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: MRLacey # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: ["https://paypal.me/mrlaceydev"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Matt Lacey
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ENAMEL - An RFC for possible future XAML-based development
2 |
3 | ## Executive Summary / TLDR
4 |
5 | A new, simplified markup language to define application UI. The new files automatically generate comparable XAML files for your WPF, .NET MAUI, or WinUI app which is then compiled as normal. Fully configurable, totally optional (use it for some or all XAML files) and low risk as the XAML (and C#) files are still available.
6 |
7 | It addresses the common complaints about having to work with XAML and adds new functionality without the need to change any of the existing tooling.
8 |
9 | How much less verbose is it?
10 |
11 | Well, this is the XAML for an Empty page in a .NET MAUI app.
12 |
13 | ```xml
14 |
15 |
19 |
20 | ```
21 |
22 | And this is the exact same thing in ENAMEL.
23 |
24 | ```enamel
25 | ContentPage
26 | ```
27 |
28 | _There are more examples comparing XAML and ENAMEL [here](./comparisons/)_
29 |
30 | ENAMEL: Simple, human readable, and easily maintainable UI markup.
31 |
32 | ## Introduction
33 |
34 | For many developers, XAML (eXtensible Application Markup Language) is seen as verbose, hard to read, and difficult to work with. There are others who describe it a "fine" or "adequate".
35 | For both groups, that there has been no significant update to XAML since its launch, almost twenty years ago, highlights that it is overdue to consider what improvements or enhancements may be appropriate.
36 |
37 | That XAML has existed for so long without the necessity for change can be considered a positive for the language as it shows reliability, stability, and the strengths of the original design.
38 | However, lots has changed in that time. Requirements, expectations, and what's possible, practical, & desirable have all evolved. Developers want, need and expect the tools and languages they use to improve over time. If they don't, when considered alongside alternatives, the tools can easily be perceived as getting worse.
39 |
40 | XAML can be written in ways that follow conventions, patterns, and "best practices" of other programming languages to greatly improve the readability and maintainability of the code. (Such as [demonstrated in this workshop](https://github.com/dotnet-presentations/dotnet-maui-workshop/tree/main/Community%20Modules/XAML).) But this still more to read than is necessary for the person wanting to understand the intent and important details in the code.
41 |
42 | This RFC was driven by a desire to ask "What could be done to improve the code used to declare a GUI interface if it wasn't limited by historic choices and restrictions of XML-based markup?" If the desire is to build "modern apps", using a twenty-year-old technology (a lifetime in tech terms) at least deserves to be questioned.
43 |
44 | This is not an attempt to persuade anyone happy with the XAML they write or the alternative they use to switch from what they are currently being productive with. Use what works for you.
45 | However, for many people currently using XAML, there is an implicit understanding, or expectation, that things could be better. This RFC explores one possible solution.
46 |
47 | ## Why changing XAML is hard
48 |
49 | Changing XAML is hard. Modifications risk breaking existing code and tooling.
50 |
51 | A complete replacement would make some people think that they need to rewrite everything they currently have or be stuck on a legacy technology that would fail to get further updates. The plethora of options for building native Windows apps (many of which use XAML) already creates this feeling in many. Any solution should not look to extend this uncertainty. Similarly, .NET MAUI is growing steadily after the conversion from Xamarin.Forms. It would not be desirable to create confusion amongst users and those considering it after such a big, recent change.
52 |
53 | An additional (official) option for defining UIs would also increase the total support overhead as XAML would still need to be supported for many years to come.
54 |
55 | Variants of XAML are used by multiple frameworks and project types. These include (but are not limited to) WPF, .NET MAUI, and WinUI. Ideally, any change or alternative to XAML should not be limited to only one of these.
56 |
57 | Creating a new, alternative language and integrating it with the tooling (IDEs, compilers, CI/CD systems) and the frameworks where it is used, has the potential to be a large amount of complex work. This risks prohibiting the number of possible alternatives that can be tried and the number of people with the skills to create such experiments. It's in no one's interest to take a lot of time (and/or spend a lot of money) on an experiment that may not turn into an actual product.
58 |
59 | Trying new languages can be risky.
60 | Trivial demos rarely give a true impression of what a language is like to build "real" applications with. Completely rewriting an existing app can be time-consuming. Building something entirely new presents potential maintenance issues if the new app is wanted but the technology used to build it is not fully adopted.
61 |
62 | Learning a new language can be hard and teaching/training can be expensive.
63 |
64 | ## A new language as a possible solution
65 |
66 | ENAMEL (**E**xperimental **N**ative **A**pplication **M**arkup **E**xtension **L**anguage) is a new proposed language that avoids and addresses the challenges mentioned above.
67 |
68 | It primarily addresses the above concerns by providing a way of creating valid XAML files that are then used by the existing tooling.
69 |
70 | 1. Write ENAMEL (in a `.enml` file)
71 | 2. When the file is saved, it automatically generates a corresponding `.xaml` file.
72 | 3. The generated XAML files (and accompanying code-behind) are used as if written by hand.
73 |
74 | The tooling to build the app ignores the new files it doesn't know about and Visual Studio's built-in capabilities to nest files means that the solution doesn't become cluttered.
75 |
76 | It's fully optional.
77 | As the tools to build an app don't need the ENAMEL files it doesn't matter if they exist for some, none, or all the XAML files. This allows for incremental adoption as there's no need to rewrite all UI files at once in a new language. Additionally the risks of experimentation are also removed as if tried but not-ultimately adopted, the project still contains all the XAML it needs to keep on working.
78 | It's also optional in the sense that anyone happy with XAML as it currently is, is free to keep using it as they currently do. Additionally, any improvements to XAML or changes to the toolchain would benefit the people using XAML and any new solution that generates it.
79 |
80 | It relies on a simple but powerful ruleset that is explained in detail below. By having such a small and specific set of rules it is easy to learn. These rules and syntax are also optimized for human readability, rather than the rigorous structure of something (XML) initially intended for reading and writing by machines.
81 |
82 | In addition to producing XAML, ENAMEL also accepts XAML as valid input. Not only does this make migrating XAML to ENAMEL a trivial process, it also makes it easy to explore how it works with more complex examples in existing codebases.
83 | An additional benefit of supporting "inline XAML" within an ENAMEL file is that it offers a fallback option for any particular elements that ENAMEL does not support or someone chooses not to convert.
84 |
85 | It's not magic, it's just source generation. Source generation is increasingly used and accepted in .NET and other technology stacks. This proposed solution may be well timed to be accepted based on the widespread adoption of source generated C# code elsewhere in project that would use this.
86 |
87 | By continuing to use the existing tool chains, ENAMEL has no direct dependency on or connection to any other technology. It could just as easily produce XAML files for use in WPF, WinUI, .NET MAUI, or anything else.
88 |
89 | It is intended to be highly configurable. This enables the flexibility to support any dialect of XAML, and it also enables optimizations based on preferred ways of working and personal preferences.
90 | All configurable settings are also optional.
91 |
92 | The simplicity of the language and the way of avoiding the need to modify existing toolchains means that experiments as to the suitability of this as a solution can be done quickly and cheaply. An initial proof-of-concept with basic Visual Studio integration was created in just two days.
93 |
94 | A new language that produces XAML does not have the same constraints as XAML itself. This makes it simple to add features and functionality that are beneficial but impossible with XAML.
95 |
96 | ## Language goals
97 |
98 | The design of the ENAMEL language has been guided by these principles:
99 |
100 | - Be logically comparable with equivalent XAML.
101 | - Avoid duplication and repetition.
102 | - Aim for brevity AND clarity in the code.
103 | - Prefer words over symbols and punctuation.
104 | - Avoid adding keywords unless they offer clear and significant benefits.
105 |
106 | The intention is to create something that anyone unfamiliar with the language would find easily understandable.
107 |
108 | ## Language rules
109 |
110 | The language is defined by a small set of rules that are also very powerful.
111 |
112 | ### Basic rules
113 |
114 | As the purpose is to create a XAML file that describes a "UI tree", an ENAMEL file also adopts a tree like structure that is representative of the `Elements` that may also have `Attributes` specified and zero or more children.
115 |
116 | #### Elements and child-elements
117 |
118 | The following rules define how to specify an Element in ENAMEL.
119 |
120 | - Elements are specified by their name only. No brackets (angled or otherwise.)
121 | - Each element starts on a different line.
122 | - Child elements are indented from their parents.
123 | - Siblings are always indented the same amount.
124 | - Indenting can be any size (and use tabs, spaces, or both), but must be consistent among siblings.
125 | - Blank lines (or containing only whitespace) _may_ be used to separate elements. These are not included in the generated output.
126 |
127 | The following (partial) example uses elements (Types) from .NET MAUI but should be clear to all.
128 |
129 | ```ascii
130 | Page
131 | VerticalStackLayout
132 | Label
133 |
134 | CollectionView
135 | ```
136 |
137 | - Child elements may optionally be surrounded by curly braces (`{` & `}`). If used, the opening brace should be on the end of the line containing the element opening. The closing brace should be on its own line and may be indented if desired.
138 | - Curly braces can be nested as appropriate.
139 |
140 | While neither are particularly useful, the following example is functionally equivalent to the one above.
141 |
142 | ```ascii
143 | Page {
144 | VerticalStackLayout {
145 | Label
146 |
147 | CollectionView
148 | }
149 | }
150 | ```
151 |
152 | - XAML elements (anything inside matching angle brackets) are assumed to be valid XAML and are output directly as input.
153 |
154 | Again, this example will produce the same output as above.
155 |
156 | ```ascii
157 | Page
158 | VerticalStackLayout
159 |
160 |
161 |
162 | ```
163 |
164 | Few elements are defined without attributes to represent the properties that are set on the types created for an element.
165 |
166 | #### Attributes
167 |
168 | The basic rules for specifying attributes are:
169 |
170 | - Attributes are defined after an element name.
171 | - Attributes can be defined on the same line as the element name or indented on immediately subsequent lines.
172 | - Attribute values do not need to be enclosed in quotes if they do not contain spaces. (Quotes will be added as necessary in the generated XAML.)
173 |
174 | The following three examples (using WinUI types) all produce the same output.
175 |
176 | All attributes on one line and all in quotes.
177 |
178 | ```ascii
179 | TextBlock Text="Hello ENAMEL!" FontSize="24" FontWeight="Bold"
180 | ```
181 |
182 | Some attributes after the element name and the rest on the next line. The FontSize value does not use quotes.
183 |
184 | ```ascii
185 | TextBlock Text="Hello ENAMEL!"
186 | FontSize=24 FontWeight="Bold"
187 | ```
188 |
189 | All attributes on their own lines. The FontSize and FontWeight values do not use quotes.
190 |
191 | ```ascii
192 | TextBlock
193 | Text="Hello ENAMEL!"
194 | FontSize=24
195 | FontWeight=Bold
196 | ```
197 |
198 | ### Other rules
199 |
200 | - It is possible to specify multiple properties that should all have the same value by concatenating the property names with ampersands (`&`) before specifying the value. e.g. `Height&Width=100`
201 |
202 | This ENAMEL:
203 |
204 | ```ascii
205 | Rectangle Height&Width=100
206 | ```
207 |
208 | Will produce this:
209 |
210 | ```xml
211 |
212 | ```
213 |
214 | - Attributes specified as child elements can be implemented by prefixing the attribute name with a dot (`.`) to remove the need to specify the element name twice.
215 |
216 | This means that the following are functionally the same:
217 |
218 | ```ascii
219 | Button
220 | Button.Content
221 | TextBlock Text="Click me"
222 | ```
223 |
224 | and
225 |
226 | ```ascii
227 | Button
228 | .Content
229 | TextBlock Text="Click me"
230 | ```
231 |
232 | - Whole lines can be treated as comments by making the first non-whitespace character a hash symbol (`#`).
233 |
234 | This ENAMEL:
235 |
236 | ```ascii
237 | # Try out comments in ENAMEL
238 | ```
239 |
240 | Will become the following in the generated XAML:
241 |
242 | ```xml
243 |
244 | ```
245 |
246 | - Individual attributes can be excluded from being included in the generated output by prefixing the name with a tilde (`~`).
247 |
248 | So, this:
249 |
250 | ```ascii
251 | TextBox ~Text="My WPF Example" Width="300" ~MaxLines=3 Margin="0,4,0,8"
252 | ```
253 |
254 | will produce:
255 |
256 | ```xml
257 |
258 | ```
259 |
260 | The ability to comment out individual attributes in XAML has been requested by many developers but is unsupported by XML. ENAMEL provides this missing feature and so makes it easier to try out the alteration (or removal) of specific attributes without having to remove them from the file. This removes the need to remember what has be changed or rely on the "undo" stack.
261 |
262 | #### Advanced rules (based on settings)
263 |
264 | Much of what is generated can be controlled by specifying settings.
265 | Settings are specified in a file named `enamel-settings.json` and can be placed anywhere in the solution (or files system). When an ENAMEL file is used to generate XAML, a settings file will be sought by looking in the same directory as the file. If a settings file is not found it will look for one in parent directories, all the way up to the root of the drive.
266 |
267 | The above examples of the XAML generated have been oversimplified.
268 | All generated files automatically have an XML prolog added. There are also two settings that impact what is generated for the root element.
269 |
270 | - An `x:Class` attribute can be automatically added if `AutoGenerateClass` is set to `true`. This adds a value based on the appropriate namespace and the class name that matches the file name.
271 | - XML namespaces (and their aliases) can also be configured in settings by specifying the `DefaultRootNamespaces` property. These values are then used in attributes added to the root element of any generated file.
272 |
273 | If the following settings file was used:
274 |
275 | ```json
276 | {
277 | "AutoGenerateClass": true,
278 | "DefaultRootNamespaces": [
279 | {
280 | "Alias": "",
281 | "Namespace": "http://schemas.microsoft.com/dotnet/2021/maui"
282 | },
283 | {
284 | "Alias": "x",
285 | "Namespace": "http://schemas.microsoft.com/winfx/2009/xaml"
286 | }
287 | ],
288 | }
289 | ```
290 |
291 | An empty .NET MAUI ContentPage could be as simple as (`MainPage.enml`):
292 |
293 | ```ascii
294 | ContentPage
295 | ```
296 |
297 | And still produce the following XAML:
298 |
299 | ```xml
300 |
301 |
306 | ```
307 |
308 | Not only does this remove a lot of the "noise" from the root element in an ENAMEL document, it also makes it much easier to know that all XML namespaces are available in every file and also that they all use the same alias for the same namespace.
309 |
310 | - How the generated XAML is formatted can be controlled. While many formatting configuration options can be imagined, the initial version of ENAMEL is proposed to support `SingleLine` and `MultiLine` as options. These options will respectively put all the attributes on the same line as the opening element tag, or put each attribute on it's own line in the generated XAML file.
311 |
312 | - How the generated XAML treats the RowDefinitions and GridDefinitions of a Grid can be controlled, to allow universal support for the simplified syntax.
313 |
314 | With the following setting set to true
315 |
316 | ```json
317 | {
318 | "ExpandGridDefinitions": true,
319 | }
320 | ```
321 |
322 | This ENAMEL
323 |
324 | ```ascii
325 | Grid RowDefinitions="*,Auto,*" ColumnDefinitions="*,12,2*"
326 | ```
327 |
328 | will produce this XAML
329 |
330 | ```xml
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 | ```
344 |
345 | This is to support frameworks that do not (yet?) support the "inline syntax" or for those who prefer the other format.
346 |
347 | ##### There are also settings to simplify the specifying of attributes
348 |
349 | - Some elements are _almost_ never used without specifying a specific attribute. (e.g. A `Label` or `TextBlock` is almost never used without setting the `Text` attribute.) Rather than repeatedly specify the name of this attribute in the ENAMEL, it can be specified in the setting as a **"default attribute"** and then used without specifying the attribute name when that element is used.
350 | To specify a value for the default attribute, it must be the first attribute specified immediately after the element name and on the same line.
351 |
352 | So, with this setting:
353 |
354 | ```json
355 | {
356 | "DefaultAttributes": [
357 | {
358 | "ElementName": "Label",
359 | "AttributeName": "Text"
360 | }
361 | ],
362 | }
363 | ```
364 |
365 | this:
366 |
367 | ```ascii
368 | Label "Hello"
369 | ```
370 |
371 | is equivalent to:
372 |
373 | ```ascii
374 | Label Text="Hello"
375 | ```
376 |
377 | And, as the attribute value doesn't include a space, it's also acceptable to write:
378 |
379 | ```ascii
380 | Label Hello
381 | ```
382 |
383 | And all three of the above examples each generate this XAML:
384 |
385 | ```xml
386 |
387 | ```
388 |
389 | Just as some attribute names are specified over and over in a file (and files), it can be common to see the names of some MarkupExtensions repeated multiple times.
390 |
391 | - Specifying a `DefaultMarkupExtension` in the settings file allows this value to be inferred when a MarkupExpression appears to not have a specified MarkupExtension.
392 |
393 | As an example, given this setting:
394 |
395 | ```json
396 | {
397 | "DefaultMarkupExtension": "Binding",
398 | }
399 | ```
400 |
401 | The following two `Labels` produce the same XAML:
402 |
403 | ```ascii
404 | Label Text="{Binding Name}"
405 |
406 | Label Text="{Name}"
407 | ```
408 |
409 | In fact, if combined with the default attribute specified previously and the fact that the attribute value has no space, the ENAMEL can become even simpler:
410 |
411 | ```ascii
412 | Label {Name}
413 | ```
414 |
415 | It doesn't take much to get used to being able to read that as "A Label with the Text bound to the Name property (of the BindingContext/DataContext)."
416 |
417 | And as you'd expect it still produces the XAML as below.
418 |
419 | ```xml
420 |
421 | ```
422 |
423 | There aren't only settings for removing the need to specify some things. There are also settings that change what's entered in an ENAMEL file.
424 |
425 | - "Expansions" allow a single element to be replaced with multiple nested elements in the generated XAML. This enables the removal of some nesting and the simplifying of the ENAMEL code.
426 |
427 | An `Expansion` can be specified that will look for an element with a specified name and expand it to multiple elements based on a comma-separated list of element names.
428 |
429 | For example, with a settings file like this:
430 |
431 | ```json
432 | {
433 | "Expansions": [
434 | {
435 | "LookFor": "ItemTemplate",
436 | "ExpandTo": "ItemTemplate,DataTemplate"
437 | }
438 | ],
439 | }
440 | ```
441 |
442 | ENAMEL like this:
443 |
444 | ```ascii
445 | ListView
446 | .ItemTemplate
447 | Grid
448 | ```
449 |
450 | can generate this XAML:
451 |
452 | ```xml
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 | ```
461 |
462 | - Element and attribute names also support defined substitutions. Any such defined values are replaced in the generated XAML.
463 |
464 | These settings:
465 |
466 | ```json
467 | {
468 | "Substitutions": [
469 | {
470 | "LookFor": "HSL",
471 | "ReplaceWith": "HorizontalStackLayout"
472 | },
473 | {
474 | "LookFor": "VSL",
475 | "ReplaceWith": "VerticalStackLayout"
476 | },
477 | {
478 | "LookFor": "RowDefs",
479 | "ReplaceWith": "RowDefinitions"
480 | },
481 | {
482 | "LookFor": "ColDefs",
483 | "ReplaceWith": "ColumnDefinitions"
484 | }
485 | ]
486 | }
487 | ```
488 |
489 | allow this ENAMEL:
490 |
491 | ```ascii
492 | VSL
493 | ```
494 |
495 | to produce:
496 |
497 | ```xml
498 |
499 | ```
500 |
501 | or
502 |
503 | ```ascii
504 | Grid RowDefs=*,* ColDefs=50,Auto
505 | ```
506 |
507 | to generate
508 |
509 | ```xml
510 |
511 | ```
512 |
513 | Speaking of Grids. Grids can be powerful controls and are often the only practical way of defining some UI layouts. However, the use of Grids can lead to complex code that requires alterations in multiple places when even a small change is needed.
514 |
515 | #### Rules relating to new keywords
516 |
517 | Improved ways of working with complex grids may come in time, but Grids are sometimes needed for simpler scenarios that still require more code than anyone should have to type.
518 |
519 | Introducing `AUTOGRID`
520 |
521 | `AUTOGRID` (**in all caps-like all ENAMEL keywords**) is like a regular Grid but it will automatically assign Row and Column values to it's direct child elements based on the defined row and column definitions.
522 |
523 | So, this:
524 |
525 | ```ascii
526 | AUTOGRID RowDefs="*,*" ColDefs="*,*"
527 | Label "Top Left"
528 | Label "Top Right"
529 | Label "Bottom Left"
530 | Label "Bottom Right"
531 | ```
532 |
533 | will generate
534 |
535 | ```xml
536 |
537 |
538 |
539 |
540 |
541 |
542 | ```
543 |
544 | In the above, Row and Column values are assigned as both have definitions. If only one is defined (as below), that's all that is output.
545 |
546 | ```ascii
547 | AUTOGRID RowDefs="Auto,*,Auto"
548 | Label "Header"
549 | Label "body"
550 | Label "Footer"
551 | ```
552 |
553 | Generates
554 |
555 | ```xml
556 |
557 |
558 |
559 |
560 |
561 | ```
562 |
563 | Nested AUTOGRIDs are not supported or recommended because Grids within Grids add extra complexity for the reader. If required, the desired output can be achieved through the use of multiple files or using the Grid element directly.
564 |
565 | - ENAMEL supports two looping constructs: `FOR` and `FOREACH`.
566 |
567 | A `FOR` loop works with numeric values.
568 |
569 | The syntax is `FOR {identifier}={lowestNumber}..{highestNumber}`.
570 | You can then reference the identifier in child elements with the syntax `${identifier}`.
571 |
572 | For each integer value in the specified range, children of the `FOR` element will be generated and with reference to the identifier being included where and if wanted.
573 |
574 | For example:
575 |
576 | ```ascii
577 | FOR i=0..4
578 | Entry Placeholder="$i" x:Name="Entry$i"
579 | ```
580 |
581 | Will generate:
582 |
583 | ```xml
584 |
585 |
586 |
587 |
588 |
589 | ```
590 |
591 | A `FOREACH` loop is similar to a `FOR` but iterates over an arbitrary list of strings.
592 |
593 | The syntax is `FOREACH {identifier}={comma separated list of values, optionally quote enclosed}`.
594 | You can then reference the identifier in child element, with the syntax `${identifier}`.
595 |
596 | So, this ENAMEL
597 |
598 | ```ascii
599 | FOREACH numb=one,two,"three hundred"
600 |
601 | Label $numb
602 | ```
603 |
604 | generates this XAML
605 |
606 | ```xml
607 |
608 |
609 |
610 | ```
611 |
612 | Nested loops are supported but will require the use of different identifiers to avoid unexpected results.
613 |
614 | The `SET` keyword is intended to help simplifying the setting of properties within a resource as an alternative to the use of a `` element when only the `Property` and `Value` need to be specified.
615 |
616 | The syntax is `SET {PropertyName}={PropertyValue}`. Quotes around the `{PropertyValue}` are optional unless needed to enclose spaces.
617 |
618 | As an example, this input
619 |
620 | ```ascii
621 | Style TargetType=Button
622 | SET Border=1
623 | SET Background=Blue
624 | ```
625 |
626 | produces:
627 |
628 | ```xml
629 |
633 | ```
634 |
635 | The aim of this keyword is to enable the removal of text that is implied through the standard syntax used elsewhere. When additional properties of the `Setter` need to be specified or more complex values are needed for the `{PropertyValue}`, this can be done without using the keyword.
636 |
637 | #### Generating multiple files
638 |
639 | An attempt to consider the future evolution of XAML would be incomplete without exploring the possibility of also incorporating C# code within the file.
640 |
641 | Rather than attempting to recreate something like Razor pages (which would require changes to the platforms using the XAML files), the approach here is to try and incorporate simple C# snippets into the file where the GUI is defined, rather than having to enter them into the "code behind" file.
642 |
643 | C# code can be specified in the value of an attribute representing an Event. To make it clear that this is "inline C#" code, the attribute value is prefixed and suffixed with at signs (`@`). This is to make it clear that the attribute value is different from other attributes and a variation on the use of curly braces to indicate Markup Expressions.
644 |
645 | The trailing `;` is not required within the ENAMEL file and will be added if not included.
646 |
647 | When the XAML is generated, the attribute value is replaced with a generated event name and an additional C# file is created containing a partial class with events that contain the C# from the .enml file.
648 |
649 | An example makes it clearer.
650 |
651 | Initial file: `InlineExamplePage.enml` (partial)
652 |
653 | ```ascii
654 | Button "operate on variables inside the code behind file"
655 | Clicked="@ count=0 @"
656 |
657 | Button "Invoke the command!"
658 | Clicked="@ vm.MyCommand.Invoke() @"
659 |
660 | Button "do something"
661 | x:Name="MyButton"
662 | Clicked="@ DoSomething() @"
663 | ```
664 |
665 | Generated file: `InlineExamplePage.xaml` (partial)
666 |
667 | ```xml
668 |
671 |
672 |
675 |
676 |
680 | ```
681 |
682 | Additional generated file: `InlineExamplePage.enml.cs` (full)
683 |
684 | ```csharp
685 | namespace MyDemoApp.Views;
686 |
687 | partial class InlineExamplePage
688 | {
689 | private void Gen_On_Button1_Clicked(object sender, EventArgs e)
690 | {
691 | count=0;
692 | }
693 |
694 | private void Gen_On_Button2_Clicked(object sender, EventArgs e)
695 | {
696 | vm.MyCommand.Invoke();
697 | }
698 |
699 | private void Gen_On_MyButton_Clicked(object sender, EventArgs e)
700 | {
701 | DoSomething();
702 | }
703 | }
704 |
705 | ```
706 |
707 | While the writing of large amounts of C# inside an ENAMEL file is probably undesirable, as a quick way to interact with or invoke something in the code behind file it may be useful.
708 | With its ability to act as a way to execute Commands on the ViewModel from a control that doesn't support invoking commands directly, it may be simpler than other "EventToCommand" solutions.
709 |
710 | ## Why this name?
711 |
712 | Not only is ENAMEL by far the best name so far considered, there are multiple factors that make it appropriate:
713 |
714 | - It's a real word and so is recognizable.
715 | - It's not a name used by any other technology-related project.
716 | - It's easy to pronounce.
717 | - It's a good acronym. (Experimental Native Application Markup Extension Language)
718 | - It's not too long. (6 letters)
719 | - The name overlaps a lot with XAML.
720 | - It can include (but is not tied to) the word "experimental" which highlights the current nature of its existence.
721 | - Enamel (of the porcelain variety) is tough, durable and pleasing to look at. All qualities desirable in this context too.
722 | - Associations to tooth enamel may be unfortunate for some people, but comparisons to a hard outer layer that offers protection from decay cannot be overlooked.
723 |
724 | ## But does it work?
725 |
726 | The ideas described above have been explored in a prototype that has been so transformational that I'm reluctant to stop using it. However, this is not yet being made public for two reasons.
727 |
728 | 1. To avoid focusing discussion on the prototype when it is the concept and possibilities of the language/solution that are more important.
729 | 2. Because it is a very minimal implementation with some bugs. I don't want to focus on fixing those bugs or supporting something that was created to prove a concept when the exact specification of the language or if it should ever be anything more than a concept are still under discussion.
730 |
731 | Here's proof it exists though:
732 |
733 | 
734 |
735 | The above animation shows 3 windows inside Visual Studio.
736 |
737 | - On the left is an .enml file in an editor window.
738 | - In the middle is the .xaml file produced from the ENAMEL.
739 | - On the right is a design-time "pre-visualization" of what the page will [approximately] look like.
740 |
741 | _Yes, this is .NET MAUI's default new page with some additional buttons at the bottom._
742 |
743 | In the animation:
744 |
745 | 1. The Spacing attribute (highlighted in pink) is uncommented in the ENAMEL and the file saved.
746 | 2. This causes the XAML to be updated (yellow highlight) and that attribute added.
747 | 3. This change is also reflected in the "pre-visualization" of the XAML.
748 |
749 | _Note. The "Pre-Visualizer" is separate to anything discussed here._
750 |
751 | ## Call to action
752 |
753 | ENAMEL is presented here as a possible way that development with XAML could be made simpler.
754 | It is not intended as _the_ solution, but as an encouragement to discuss what might be.
755 |
756 | XAML has not seen significant improvement in far too long and other proposals offer only small, incremental change. **Let's be bold and consider a big change!**
757 |
758 | Please create or add to issues with any thoughts, comments, concerns, suggestions, ideas, etc.
759 |
760 | Alternatively, I can be contacted via [most social media sites](https://mrlacey.com/social).
761 |
762 | _p.s._
763 |
764 | There's more on the background to the creation of this proposal in both [short](https://www.mrlacey.com/2025/03/the-state-future-of-xaml-summary-of.html) and [long](https://www.mrlacey.com/2025/03/my-talk-at-maui-day-in-london.html) forms on my blog.
765 |
--------------------------------------------------------------------------------
/comparisons/maui-default-page.md:
--------------------------------------------------------------------------------
1 | # A default new .NET MAUI page
2 |
3 | This is what a default new page for .NET MAUI looks like in XAML:
4 |
5 | ```xaml
6 |
7 |
10 |
11 |
12 |
15 |
20 |
21 |
25 |
26 |
31 |
32 |
38 |
39 |
40 |
41 |
42 | ```
43 |
44 | This is the exact same thing in equivalent ENAMEL:
45 |
46 | ```ascii
47 | ContentPage
48 | ScrollView
49 | VerticalStackLayout
50 | Padding=30,0
51 | Spacing=25
52 |
53 | Image dotnet_bot.png
54 | HeightRequest=185
55 | Aspect=AspectFit
56 | SemanticProperties.Description="dot net bot in a hovercraft number nine"
57 |
58 | Label "Hello, World!"
59 | Style={Headline}
60 | SemanticProperties.HeadingLevel=Level1
61 |
62 | Label "Welcome to
.NET Multi-platform App UI"
63 | Style={SubHeadline}
64 | SemanticProperties.HeadingLevel=Level2
65 | SemanticProperties.Description="Welcome to dot net Multi platform App U I"
66 |
67 | Button "Click me"
68 | x:Name=CounterBtn
69 | SemanticProperties.Hint="Counts the number of times you click"
70 | Clicked=OnCounterClicked
71 | HorizontalOptions=Fill
72 | ```
73 |
74 | The XAML was 1316 characters over 36 lines.
75 | The ENAMEL is 887 characters over 25 lines.
76 |
77 | ---
78 |
79 | A better version of the ENAMEL (as could easily be created by a developer with XAML experience) could look like this.
80 |
81 | ```ascii
82 | VerticallyScrollingPage
83 |
84 | Image dotnet_bot.png
85 | HeightRequest=185
86 | SemanticProperties.Description="dot net bot in a hovercraft number nine"
87 |
88 | Headline "Hello, World!"
89 |
90 | SubHeadline "Welcome to
.NET Multi-platform App UI"
91 | SemanticProperties.Description="Welcome to dot net Multi platform App U I"
92 |
93 | Button "Click me"
94 | SemanticProperties.Hint="Counts the number of times you click"
95 | Clicked=OnCounterClicked
96 | ```
97 |
98 | This still produces the exact same output (while relying on simple styles and classes specified elsewhere) but is only 458 characters over 14 lines.
99 | This is less than 40% the size of the original XAML but is more readable and easier to understand.
100 |
--------------------------------------------------------------------------------
/comparisons/maui-sample-page.md:
--------------------------------------------------------------------------------
1 | # A new .NET MAUI page with sample content
2 |
3 | This is what a new page containing the SyncFusion sample content for .NET MAUI looks like in XAML:
4 |
5 | ```xaml
6 |
7 |
17 |
18 |
19 |
22 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
72 |
73 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
89 |
90 |
91 | ```
92 |
93 | And here's the direct equivalent in ENAMEL:
94 |
95 | ```xaml
96 | ContentPage x:DataType="pageModels:MainPageModel"
97 | Title="{Binding Today}"
98 | NavigatedTo="@ NavigatedToCommand.Invoke(); @"
99 | NavigatedFrom="@ NavigatedFromCommand.Invoke(); @"
100 | Appearing="@ AppearingCommand.Invoke(); @"
101 |
102 | .Resources
103 | toolkit:InvertedBoolConverter x:Key="InvertedBoolConverter"
104 |
105 | Grid
106 | pullToRefresh:SfPullToRefresh
107 | IsRefreshing={IsRefreshing}
108 | RefreshCommand={RefreshCommand}
109 | .PullableContent
110 | ScrollView
111 | VerticalStackLayout Spacing="{StaticResource LayoutSpacing}" Padding="{StaticResource LayoutPadding}"
112 | controls:CategoryChart
113 |
114 | Label Text="Projects" Style="{StaticResource Title2}"
115 |
116 | ScrollView Orientation=Horizontal Margin="-30,0"
117 | HorizontalStackLayout
118 | Spacing=15 Padding="30,0"
119 | BindableLayout.ItemsSource={Projects}
120 | .ItemTemplate
121 | DataTemplate x:DataType="models:Project"
122 | controls:ProjectCardView WidthRequest="200"
123 | .GestureRecognizers
124 | TapGestureRecognizer Command="{NavigateToProjectCommand, Source={RelativeSource AncestorType={x:Type pageModels:MainPageModel}}, x:DataType=pageModels:MainPageModel}" CommandParameter={.}
125 |
126 | Grid HeightRequest="44"
127 | Label "Tasks" Style="{StaticResource Title2}" VerticalOptions=Center
128 |
129 | ImageButton
130 | Source="{StaticResource IconClean}"
131 | HorizontalOptions=End
132 | VerticalOptions&Aspect=Center
133 | HeightRequest&WidthRequest=44
134 | IsVisible={HasCompletedTasks}
135 | Command={CleanTasksCommand}
136 |
137 | VerticalStackLayout Spacing="15" BindableLayout.ItemsSource="{Tasks}"
138 | BindableLayout.ItemTemplate
139 | controls:TaskView
140 | TaskCompletedCommand="{TaskCompletedCommand, Source={RelativeSource AncestorType={x:Type pageModels:MainPageModel}}, x:DataType=pageModels:MainPageModel}"
141 |
142 | controls:AddButton {AddTaskCommand}
143 | IsEnabled="{IsBusy, Converter={StaticResource InvertedBoolConverter}}"
144 | ```
145 |
146 | The 85 lines of XAML can be represented in just 49 lines of ENAMEL.
147 |
148 | This is a direct comparison with what is in the default templates.
149 | However, both could be made significantly simpler and more maintainable by applying good coding practices, but this is beyond the scope of this document.
150 |
--------------------------------------------------------------------------------
/comparisons/readme.md:
--------------------------------------------------------------------------------
1 | # Comparisons between XAML and ENAMEL files
2 |
3 | This directory contains examples of default XAML files (from .NET MAUI, WinUI, and WPF project) and shows the equivalent ENAMEL file contents:
4 |
5 | - [.NET MAUI - Default Page](./maui-default-page.md)
6 | - [.NET MAUI - Sample Page](./maui-sample-page.md)
7 | - [WinUI3 - New Window](./winui-new-window.md)
8 | - [WPF - New Window](./wpf-new-window.md)
9 |
--------------------------------------------------------------------------------
/comparisons/winui-new-window.md:
--------------------------------------------------------------------------------
1 | # A default new WinUI Window
2 |
3 | This is what the MainWindow of a new WinUI app looks like in XAML:
4 |
5 | _Note. The latest templates do not include the StackPanel and button, but they are included here to show a variation from the WPF comparison example._
6 |
7 | ```xaml
8 |
9 |
18 |
19 |
20 |
21 |
22 |
23 | ```
24 |
25 | And the equivalent ENAMEL:
26 |
27 | ```ascii
28 | Window Title=App1
29 |
30 | StackPanel Orientation=Horizontal HorizontalAlignment&VerticalAlignment=Center
31 |
32 | Button x:Name="myButton" Click="@ myButton.Content = "Clicked"; @"
33 | .Content
34 | Click Me
35 | ```
36 |
37 | Note also that the ENAMEL version also removes the need for the EventHandler in the XAML code-behind files.
38 |
--------------------------------------------------------------------------------
/comparisons/wpf-new-window.md:
--------------------------------------------------------------------------------
1 | # A default new WPF Window
2 |
3 | This is what the MainWindow of a new WPF app looks like in XAML:
4 |
5 | ```xaml
6 |
14 |
15 |
16 |
17 |
18 | ```
19 |
20 | And the equivalent ENAMEL:
21 |
22 | ```ascii
23 | Window Title="MainWindow" Height=450 Width=800
24 | Grid
25 | ```
26 |
27 | Yes, the difference really is that dramatic.
28 |
--------------------------------------------------------------------------------
/enamel-tease.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/enamel-tease.gif
--------------------------------------------------------------------------------
/slides/00_the-basics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/00_the-basics.png
--------------------------------------------------------------------------------
/slides/01_root-elements.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/01_root-elements.png
--------------------------------------------------------------------------------
/slides/02_optional-braces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/02_optional-braces.png
--------------------------------------------------------------------------------
/slides/03_properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/03_properties.png
--------------------------------------------------------------------------------
/slides/04_properties2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/04_properties2.png
--------------------------------------------------------------------------------
/slides/05_default-properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/05_default-properties.png
--------------------------------------------------------------------------------
/slides/06_multi-properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/06_multi-properties.png
--------------------------------------------------------------------------------
/slides/07_default-markuExtension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/07_default-markuExtension.png
--------------------------------------------------------------------------------
/slides/08_comments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/08_comments.png
--------------------------------------------------------------------------------
/slides/09_substitutions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/09_substitutions.png
--------------------------------------------------------------------------------
/slides/10_expansions-dotattributes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/10_expansions-dotattributes.png
--------------------------------------------------------------------------------
/slides/11_for-loops.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/11_for-loops.png
--------------------------------------------------------------------------------
/slides/12_foreach-loops.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/12_foreach-loops.png
--------------------------------------------------------------------------------
/slides/13_autogrids.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/13_autogrids.png
--------------------------------------------------------------------------------
/slides/14_inlinecsharp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrlacey/enamel/ed2ea50bafb62ecdcef23899c42e071a405db7a2/slides/14_inlinecsharp.png
--------------------------------------------------------------------------------
/slides/readme.md:
--------------------------------------------------------------------------------
1 | # Slides
2 |
3 | The slides in this directory are from a presentation showing an early version.
4 | They do not match directly with what's in the root document (especially regarding settings formats) but may be easier to read.
5 |
--------------------------------------------------------------------------------