<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mitch Stanley</title>
    <description>The latest articles on DEV Community by Mitch Stanley (@mitchartemis).</description>
    <link>https://dev.to/mitchartemis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F63099%2Fca83dc38-d373-4396-b531-03e4ef5e4a4b.jpg</url>
      <title>DEV Community: Mitch Stanley</title>
      <link>https://dev.to/mitchartemis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mitchartemis"/>
    <language>en</language>
    <item>
      <title>Desktop GUIs for Web Developers</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Thu, 20 Jan 2022 19:48:33 +0000</pubDate>
      <link>https://dev.to/mitchartemis/desktop-guis-for-web-developers-2i9d</link>
      <guid>https://dev.to/mitchartemis/desktop-guis-for-web-developers-2i9d</guid>
      <description>&lt;p&gt;This is a cross-post from website - &lt;a href="https://www.fullstackstanley.com/articles/desktop-guis-for-webdevelopers/" rel="noopener noreferrer"&gt;check the original here&lt;/a&gt; 😀&lt;br&gt;
Over the past few years I’ve become more interested in making desktop applications. For some context, I’m a web developer with around 15 years of experience. I mostly work with Laravel and Vue.JS but I have dabbled in a whole heap of other languages and frameworks.&lt;/p&gt;

&lt;p&gt;I love a good desktop app and where possible I’ll generally prefer having an app rather than visiting a website. I’ve also had to turn websites into desktop apps at my job so I’ve explored a fair few different technologies for this purpose.&lt;/p&gt;

&lt;p&gt;I’ve written this blog to share the desktop technologies that I'm currently interested in. Bear in mind, some of these tools I’ve built full apps with and some I’ve only gone through briefly with tutorials. I’ll make this clear throughout the article.&lt;/p&gt;

&lt;p&gt;I hope to have given you an idea of what to look for when choosing a desktop application framework. Hint: There is no golden ticket, each tool has pros and cons. All I can give you is my experience with each of them and when you should consider them for your projects.&lt;/p&gt;

&lt;p&gt;The tools I'll be reviewing are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compose Multiplatform&lt;/li&gt;
&lt;li&gt;egui&lt;/li&gt;
&lt;li&gt;Electron

&lt;ul&gt;
&lt;li&gt;Ember Electron&lt;/li&gt;
&lt;li&gt;Quasar&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flutter&lt;/li&gt;
&lt;li&gt;React Native for Windows&lt;/li&gt;
&lt;li&gt;Tauri&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What to look for in a GUI tool
&lt;/h2&gt;

&lt;p&gt;There are almost as many GUI tools as there are frontend Javascript frameworks.  So how do you pick one for the project you’re working on?&lt;/p&gt;

&lt;p&gt;If you use a Javascript framework for the web, a good place to start is to see if there is a desktop counterpart for that library. For example, Quasar for Vue developers, &lt;a href="https://microsoft.github.io/react-native-windows/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; for React developers, Ember Electron for Ember developers and so on.&lt;/p&gt;

&lt;p&gt;Two of the three mentioned above are Electron based tools and I think it’s worth pointing out, if you want something built fast with access to a large community, ecosystem and is regularly updated then Electron is absolutely worth investigating. It gets a lot of gripe because release builds have a large file size, it’s not as fast as native, and generally most apps don’t feel quite right, but these downsides can often be forgiven.&lt;/p&gt;

&lt;p&gt;As with all of the tools that I mention below, you have to weigh up various concerns.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Who your application is for?&lt;/strong&gt;—do the users care about it being a wrapper for a web application? Can the tool provide the functionality that your users expect?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How complex is the project likely to be?&lt;/strong&gt;—are there frequent updates that need to be kept up to date with web/mobile counter-parts?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The size of the team working on the project&lt;/strong&gt;—single developer or a large team? Trying to keep two code bases up to date (e.g. a website and a desktop app) for a single developer could literally half their productivity. Not so much of an issue for a small team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How fast does it need to be built?&lt;/strong&gt; Exploring new technology takes time and some tools are easier to grok than others, have larger communities to help, and have plugins to solve various problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility.&lt;/strong&gt; Unless your making a personal project, you should try to add some level of accessibility to your application. The more the better, but not every tool makes this easy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With those key points in mind, there are a few additional things to think about&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What platforms do you want to build for?&lt;/strong&gt; Not all tools work on every platform. For example, React Native does not build for Linux but does work on iOS and Android. SwiftUI does not build for Linux or WIndows, but the code can be shared with all of the Apple Ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribution and updates.&lt;/strong&gt; Do you want to distribute via Apple's App Store, the Microsoft Store? Linux has various options for automatic updates including Snaps and AppImages. For MacOS and Windows there’s also options for updates via your own server, or you could let user base to update manually.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support.&lt;/strong&gt; Is the library actively maintained and how often is it updated?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Should you choose boring technology?&lt;/strong&gt; Small side projects can be a fun excuse to try a new stack, but if you're building a product for a company with customers reliant on stable software then you should probably pick something that’s been battle-tested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level of native integration.&lt;/strong&gt; Native isn’t necessarily boolean value. You can use web based technologies for the core application but still support native APIs for window management, menu/tray support, storage, notifications, widgets and more. Electron for example, has great options for all of those features. Some of the newer/smaller libraries tend to fall short in this regard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, if you’re not familiar with a front end javascript library—maybe because you’re a backend developer—You might also want to look into libraries for programming languages that you’re familiar with. There are often wrappers for existing technologies like GTK, FLTK, Qt. For example, &lt;a href="https://github.com/fltk-rs/fltk-rs" rel="noopener noreferrer"&gt;FLTK-rs&lt;/a&gt; for Rust, or the &lt;a href="https://www.rubydoc.info/gems/gtk3/3.4.3" rel="noopener noreferrer"&gt;GTK3 gem for Ruby&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  So, what’s out there?
&lt;/h2&gt;

&lt;p&gt;Here comes the fun stuff. Obviously I can’t go through every single option available, but I will show you what has piqued my interest&lt;/p&gt;
&lt;h3&gt;
  
  
  Compose Multiplatform
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FF5900E3A-0221-415F-8C32-DAA56C31811E_2%2Fkvjc8Y6ojiAySt3FNyjRvGctLJj4AcyxfwLoPV5LY18z%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FF5900E3A-0221-415F-8C32-DAA56C31811E_2%2Fkvjc8Y6ojiAySt3FNyjRvGctLJj4AcyxfwLoPV5LY18z%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not to be confused with &lt;a href="https://developer.android.com/jetpack/compose" rel="noopener noreferrer"&gt;Jetpack Compose&lt;/a&gt;, the modern toolkit for building Android apps, JetBrains’ Compose Multiplatform is based on the same technology but allows you to build for Windows/MacOS, Linux &lt;em&gt;and&lt;/em&gt; the web.&lt;/p&gt;

&lt;p&gt;Compose uses Kotlin and my opinion, this language feels great. So far I’ve run through the &lt;a href="https://www.raywenderlich.com/26791460-compose-for-desktop-get-your-weather" rel="noopener noreferrer"&gt;Ray Wenderlich tutorial by Roberto Orgiu&lt;/a&gt; and I enjoyed the experience. However, there is a lack of resources available for learning it. This tutorial and the official docs and examples are the only things I've come across.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Sunny Desk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;WeatherScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned on the website, it supports keyboard shortcuts, window manipulation, and notifications. It renders with Skia which means your apps will have native performance, however, you will need to build your own ‘Widgets’ or find an existing library if you want your app to actually &lt;em&gt;look&lt;/em&gt; native to each platform.&lt;/p&gt;

&lt;p&gt;Code sharing between Compose Multiplatform and the Jetpack Compose is possible, too, but I believe most of the UI elements have to be built separately. Still, this is a lot of platform support and I’m genuinely excited to see where this framework goes in the future.&lt;/p&gt;

&lt;p&gt;Here’s some example code to get a feel of what it looks like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F147791a0-3922-46d5-9caa-0079805af89b" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F147791a0-3922-46d5-9caa-0079805af89b" alt="image.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.desktop.DesktopMaterialTheme&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.foundation.ContextMenuDataProvider&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.foundation.ContextMenuItem&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.foundation.layout.*&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.foundation.text.selection.SelectionContainer&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.material.Text&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.material.TextField&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.runtime.mutableStateOf&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.runtime.remember&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.ui.ExperimentalComposeUiApi&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.ui.Modifier&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.ui.unit.dp&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.ui.window.singleWindowApplication&lt;/span&gt;

&lt;span class="nd"&gt;@OptIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExperimentalComposeUiApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;androidx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foundation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ExperimentalFoundationApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;singleWindowApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Compose for Desktop"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;DesktopMaterialTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;//it is mandatory for Context Menu&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello!"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;ContextMenuDataProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                        &lt;span class="nc"&gt;ContextMenuItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User-defined Action"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/*do something here*/&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;  
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;onValueChange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
                &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nc"&gt;Spacer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  

            &lt;span class="nc"&gt;SelectionContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; 
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Positives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Works on MacOS/WIndows/Linux and the web.&lt;/li&gt;
&lt;li&gt;Support for sharing code on Android apps with Jetpack Compose&lt;/li&gt;
&lt;li&gt;Uses Kotlin&lt;/li&gt;
&lt;li&gt;Native performance&lt;/li&gt;
&lt;li&gt;Built in Previews&lt;/li&gt;
&lt;li&gt;Has tools for automated testing&lt;/li&gt;
&lt;li&gt;Supported by Jetbrains&lt;/li&gt;
&lt;li&gt;Actively developed&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Negatives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Maturity - 1.0 only recently released&lt;/li&gt;
&lt;li&gt;Small community&lt;/li&gt;
&lt;li&gt;Only stand alone builds are currently supported (Although there is an Apple App Store PR), no sign of how to handle automatic updates.&lt;/li&gt;
&lt;li&gt;Small ecosystem (plugins etc)&lt;/li&gt;
&lt;li&gt;No native UI widgets or theming out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  egui
&lt;/h3&gt;

&lt;p&gt;egui is a Rust library and builds natively with Glium (Or Glow) and WASM for the web. For native, It supports MacOS, Linux, Windows.&lt;/p&gt;

&lt;p&gt;Out of the Rust GUI libraries available I think this one is my personal favourite. It’s self-described as easy to use and difficult to make mistakes. For someone like myself—who’s more of a visitor to the Rust language—it’s music to my ears.&lt;/p&gt;

&lt;p&gt;It’s actively maintained, with a new release out literally an hour ago as of the creation of this sentence.&lt;/p&gt;

&lt;p&gt;Here’s a snippet taken from one of the examples along with the newly added context menu support (Right clicking UI elements).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;egui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;CentralPanel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The central panel the region left after adding TopPanel's and SidePanel's&lt;/span&gt;

  &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.heading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"eframe template"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.hyperlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://github.com/emilk/eframe_template"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;egui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;github_link_file!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"https://github.com/emilk/eframe_template/blob/master/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"Source code."&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test Context Menu"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Sense&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.context_menu&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Open"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.clicked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.close_menu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.separator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cancel"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.clicked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="nf"&gt;.close_menu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nn"&gt;egui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;warn_if_debug_build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FE30AE296-2B93-45D1-88BC-FA516B261A28_2%2FvQecbGbBM9xJfdzCMXb57Rsl17qEjK5tBu4yJiXc7ioz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FE30AE296-2B93-45D1-88BC-FA516B261A28_2%2FvQecbGbBM9xJfdzCMXb57Rsl17qEjK5tBu4yJiXc7ioz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Positives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Works on MacOS, WIndows and Linux and the web.&lt;/li&gt;
&lt;li&gt;Built with Rust&lt;/li&gt;
&lt;li&gt;Native performance&lt;/li&gt;
&lt;li&gt;Actively developed&lt;/li&gt;
&lt;li&gt;Support for multiple renderers&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Negatives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Maturity - Not currently at a 1.0 release so the API is unstable and missing features&lt;/li&gt;
&lt;li&gt;Small community&lt;/li&gt;
&lt;li&gt;Only stand-alone builds are currently supported (Although there is an Apple App Store PR), no sign of how to handle automatic updates.&lt;/li&gt;
&lt;li&gt;No ecosystem (plugins etc)&lt;/li&gt;
&lt;li&gt;No native UI widgets or theming out of the box&lt;/li&gt;
&lt;li&gt;No live preview&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Electron
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FA90F8135-9D9B-429A-9204-164A3A906A55_2%2FcqWS1KHuLHY3bdcHZT3WHSyc2ycKHxBOFkL52qI5VS0z%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FA90F8135-9D9B-429A-9204-164A3A906A55_2%2FcqWS1KHuLHY3bdcHZT3WHSyc2ycKHxBOFkL52qI5VS0z%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve built two and a half apps with Electron so it’s fair to say I have experienced first hand the positives and negatives of the platform. Electron is a tool that puts web technologies on the desktop via Chrome. With Electron you’ll most likely be writing every part of the app with Javascript or Typescript, although it’s certainly possible to switch this up, for example, 1Password recently switched their desktop app to Electron with a Rust backend.&lt;/p&gt;

&lt;p&gt;I’ve used Electron with Ember Electron and with Quasar (Vue.JS). I'll talk more about both individually below, but as a general overview, Electron is fantastic and easy to recommend so long as you can put up with its shortcomings&lt;/p&gt;

&lt;h4&gt;
  
  
  Positives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Works on MacOS, Windows, and Linux&lt;/li&gt;
&lt;li&gt;Since it’s wrapping a web app, you can likely share most, of the code base with an web app if you have one&lt;/li&gt;
&lt;li&gt;Large community and ecosystem&lt;/li&gt;
&lt;li&gt;Support for many forms of distribution, including automatic updates and various app stores&lt;/li&gt;
&lt;li&gt;Has Chrome’s accessibility features built in&lt;/li&gt;
&lt;li&gt;Supports multiple windows, and some native components such as dialog boxes, notifications, etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Negatives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Large file size due to bundling Chrome&lt;/li&gt;
&lt;li&gt;Generally slower than the alternatives&lt;/li&gt;
&lt;li&gt;Web wrapper—projects will look and feel out of place with the operating system&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.electronjs.org/docs/latest/tutorial/security" rel="noopener noreferrer"&gt;There are many security practices to follow to keep your app secure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Ember Electron
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F0504C5EA-3F32-407A-AA3A-28AF2079AA43_2%2F17JewWI1zxPNBxaX3zOEGaSMBV58gBfxZuyGPGDycYgz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F0504C5EA-3F32-407A-AA3A-28AF2079AA43_2%2F17JewWI1zxPNBxaX3zOEGaSMBV58gBfxZuyGPGDycYgz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ember is one of my favourite Javascript frameworks. I’ve built many web projects with it so it was natural for me to try a desktop app with it, too. My apps, &lt;a href="https://snipline.io" rel="noopener noreferrer"&gt;Snipline 1 and 2&lt;/a&gt;, are both built with Ember Electron so I have a reasonable amount of experience with it.&lt;/p&gt;

&lt;p&gt;All of the positives and negatives from the Electron section still apply here, so I’ll comment specifically of the Ember Electron add-on.&lt;/p&gt;

&lt;p&gt;With Ember Electron 2, it was tricky to update the Electron dependency, but with the release of Ember Electron 3 the Electron Forge dependency was updated . This means that Electron can be kept up to date separately to Ember Electron. Since Electron gets updated quite regularly it's a much welcome update.&lt;/p&gt;

&lt;p&gt;Activity is much slower now with Ember Electron, with the latest release of 3.1.0 back in May, and the community is very small compared to the other available choices. As much as I enjoy the stack, I could not recommend it unless you want to turn an existing Ember app into a desktop app, or are already very productive with Ember.&lt;/p&gt;

&lt;h4&gt;
  
  
  Quasar
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FD40A2D8E-CD49-4EC5-9BCC-EE65F90F8BBB_2%2FYq9YbzOcmTxcJOiqaLM4c9CHcinyBlytlncYJpAgVWoz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FD40A2D8E-CD49-4EC5-9BCC-EE65F90F8BBB_2%2FYq9YbzOcmTxcJOiqaLM4c9CHcinyBlytlncYJpAgVWoz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Calling Quasar an Electron wrapper is selling it short. It provides many of the benefits of Ember JS, such as file directory conventions and a CLI, but it also adds support for mobile apps, SPAs and it’s own UI framework. Take a look at all of the reasons that make Quasar great on their &lt;a href="https://quasar.dev/introduction-to-quasar" rel="noopener noreferrer"&gt;Why Quasar?&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;I’ve built one desktop app with Quasar for an internal company project, and overall it was a pleasant experience. I much prefer Tailwind CSS to Quasar UI, and there’s nothing stopping you using both except for the additional dependency.&lt;/p&gt;

&lt;p&gt;As with Ember Electron, you get all of the benefits of Electron with Quasar, and building the app is as simple as running a command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;quasar build -m electron&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One difference from Ember Electron is the build module. Ember Electron uses ‘Electron Forge’ whereas Quasar gives you two choices, Packager or Builder. Personally, I’ve used Builder and had no issues besides the teething problems of getting auto updating working on Windows.&lt;/p&gt;

&lt;p&gt;Regarding activity, Quasar is very active, with an update to the main repo just for days ago as of writing and plenty recently before that. There are many contributors, and the documentation is great. I think that if you’re familiar with Vue.JS and Vuex then you’re in safe hands using Quasar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FA6808A14-1727-4CC2-B4FD-9C904211635A_2%2FudX0W4TcCe5ZvZAxzAoMw0T67TXTdYCOocEpPYyOykIz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FA6808A14-1727-4CC2-B4FD-9C904211635A_2%2FudX0W4TcCe5ZvZAxzAoMw0T67TXTdYCOocEpPYyOykIz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the most impressive things about Flutter is the breadth of devices it supports. From mobile, desktop, to embedded devices. Similar to Compose, it uses Skia to render the UI, so while you’re getting native &lt;em&gt;performance&lt;/em&gt; you’re most likely not going to get a native &lt;em&gt;look&lt;/em&gt;, at least not out of the box.&lt;/p&gt;

&lt;p&gt;Unlike Compose, I was pleasantly surprised when I followed an Android tutorial to build a Windows app and it just &lt;em&gt;worked&lt;/em&gt;. Of course, it looked like an Android app, with the default Material theme, but there's nothing to stop you tweaking the theme per device. &lt;a href="https://blog.whidev.com/native-looking-desktop-app-with-flutter/" rel="noopener noreferrer"&gt;Take a look at this blog post by Minas Giannekas on how he built Shortcut Keeper and how he themed it for each platform&lt;/a&gt;. Truly impressive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F69DEB148-2A26-4627-AA20-52A20334C696_2%2FxAqExkZjbxmcZbzexUce86eYxCiS9MSTAObsziEmAmYz%2FImage" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F69DEB148-2A26-4627-AA20-52A20334C696_2%2FxAqExkZjbxmcZbzexUce86eYxCiS9MSTAObsziEmAmYz%2FImage" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s also a large community and ecosystem surrounding Flutter, so you’re unlikely to run out of learning resources.&lt;/p&gt;

&lt;p&gt;But Flutter isn’t without it’s shortcomings. There’s a long list of issues in their Github repo, which also speaks for the popularity of the library. Much of the ecosystem is focused on mobile, which means if you wish to make an app work across mobile, desktop, and web, you may have to provide your own functionality for the latter two environments.&lt;/p&gt;

&lt;p&gt;There are also complaints that the development of Flutter outpaces the plugins surrounding it. You may need to stay on an older version of Flutter because of plugin compatibility issues.&lt;/p&gt;

&lt;h4&gt;
  
  
  Positives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Native performance&lt;/li&gt;
&lt;li&gt;Works on MacOS, Windows, Linux, iOS, Android, and Embedded devices&lt;/li&gt;
&lt;li&gt;Large community and lots of plugins&lt;/li&gt;
&lt;li&gt;Actively developed and backed by Google&lt;/li&gt;
&lt;li&gt;Large pool of resources to learn from&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Negatives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Most of the community and plugins are mobile focused&lt;/li&gt;
&lt;li&gt;Fast pace of development may mean compatibility issues with plugins&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reactive Native for Windows
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FE0093C9B-A25E-485E-A967-3D7E84D7A2BC_2%2FMxYznMRTjIjX24ZefqIW67yy3c50zJFn7YFulVsQpzQz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FE0093C9B-A25E-485E-A967-3D7E84D7A2BC_2%2FMxYznMRTjIjX24ZefqIW67yy3c50zJFn7YFulVsQpzQz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since I’ve included a Vue.JS and an Ember JS library, I thought it would only be fair to include a library for React developers, too.  React Native is a popular solution for building native apps for iOS and Android and uses Objective-C and Java under the hood for each platform respectively.&lt;/p&gt;

&lt;p&gt;For Windows, it renders with Universal Windows Platform (Or UWP for short) which means you really are getting native controls rendered. I couldn’t find any information of how the React Native for MacOS renders, although I’d imagine it’s doing something similar to iOS.&lt;/p&gt;

&lt;p&gt;Here’s a quick snippet that I tried starting from the base RNW project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F13e15cc3-3f0a-4aae-ae7e-bee539b8ec85" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F13e15cc3-3f0a-4aae-ae7e-bee539b8ec85" alt="Screenshot 2022-01-01 165853.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;SafeAreaView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ScrollView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;StatusBar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;useColorScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Pressable&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DebugInstructions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;LearnMoreLinks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ReloadInstructions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native/Libraries/NewAppScreen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Section&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDarkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useColorScheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDarkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useColorScheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;timesPressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTimesPressed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backgroundStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;backgroundcolor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isDarkMode&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;darker&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lighter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,](&lt;/span&gt;&lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SafeAreaView&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;backgroundStyle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StatusBar&lt;/span&gt; &lt;span class="nx"&gt;barStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDarkMode&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light-content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark-content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ScrollView&lt;/span&gt;
            &lt;span class="nx"&gt;contentInsetAdjustmentBehavior&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;automatic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;backgroundStyle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Section&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;React Native for Windows&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Section&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Pressable&lt;/span&gt;
                    &lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;setTimesPressed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;}}&lt;/span&gt;
                &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{({pressed}) =&amp;gt; [
                {
                backgroundColor: pressed ? 'rgb(210, 230, 255)'
                : 'black',
                padding: 10,
                textAlign: 'center'
                },
                styles.wrapperCustom
                ]}&amp;gt;
                {({ pressed }) =&amp;gt; (
                    &amp;lt;Text style={() =&amp;gt; [ { ...styles.text, textAlign: 'center' }]}&amp;gt;
                        {pressed ? 'Pressed!' : `Count: ${timesPressed}`}
                    &amp;lt;/Text&amp;gt;
                )}
                &amp;lt;/Pressable&amp;gt;
            &amp;lt;/ScrollView&amp;gt;
        &amp;lt;/SafeAreaView&amp;gt;
    );
};

const styles = StyleSheet.create({
    sectioncontainer: {
        margintop: 32,
        paddinghorizontal: 24
    },
    sectiontitle:
        fontsize: 24,
        fontweight: '600',
    },
    sectiondescription: {
        margintop: 8,
        fontsize: 18,
        fontweight: '400',
    },
    highlight: {
        fontweight: '700',
    },
});

export default App;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In terms of community, you have the foundation of the mobile RN community to work with, but as with other ecosystems in this article, you probably aren’t going to find much plugin support for desktop at the moment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Positives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Renders natively&lt;/li&gt;
&lt;li&gt;Share code with React Native mobile apps&lt;/li&gt;
&lt;li&gt;Build with Javascript or Typescript&lt;/li&gt;
&lt;li&gt;React developers will feel right at home&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Negatives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;RNW and MacOS are relatively new and not yet stable&lt;/li&gt;
&lt;li&gt;Smaller community and ecosystem for desktop&lt;/li&gt;
&lt;li&gt;No Linux support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SwiftUI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FC39BAB3C-89CC-454F-8337-468BB4DC2867_2%2FxwysTbzKuSRA1qxyYTApzuS7OTBa0xwzWVxwtzHi67Mz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FC39BAB3C-89CC-454F-8337-468BB4DC2867_2%2FxwysTbzKuSRA1qxyYTApzuS7OTBa0xwzWVxwtzHi67Mz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having released 2 apps and another on the way, SwiftUI is another tool I have plenty of experience with.&lt;/p&gt;

&lt;p&gt;SwiftUI has been designed by Apple to work well on each of their platforms. There are many 'Widgets' that can be shared across each platform so you can write code once and have it run on most devices. For example, context menu's on an iOS device are triggered from a long press, where as on a Mac it's triggered from a right click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Taken from the useful app, SwiftUI Companion   &lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ExampleView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Press, hold and release"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextMenu&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Open"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"open..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
         &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Delete"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"delete..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
         &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"More info..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"more..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A personal favourite feature of mine, which I’ve yet to see in other GUI frameworks, is data binding between multiple windows. Using the  &lt;code&gt;@AppStorage&lt;/code&gt; property wrapper, you can update a value in one window and have it's value easily sync in another. This is &lt;em&gt;really&lt;/em&gt; useful for preferences which are generally in their own window in MacOS apps.&lt;/p&gt;

&lt;p&gt;Here’s a truncated example of the power and simplicity of SwiftUI for Mac apps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;RsyncinatorApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@AppStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;showVisualHints&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;showVisualHints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cp"&gt;#if os(macOS)&lt;/span&gt;
    &lt;span class="kt"&gt;Settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;SettingsView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="cp"&gt;#endif&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SettingsView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;Tabs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;general&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;advanced&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;TabView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;GeneralSettingsView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tabItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kt"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"General"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"gear"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Tabs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;general&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;GeneralSettingsView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@AppStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"showVisualHints"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;showVisualHints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Show visual hints"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$showVisualHints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s the Preferences window that’s generated. If you’re familiar with Mac apps, you should recognise the general layout with the tabbed sections at the top. All this is laid out for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F4269F254-4B2A-4838-93F9-D9F6A5F5EBFB_2%2FJVmsG5gbeSkqUr5rhV7sA9s780ZkBDy1riLroKNsZLwz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F4269F254-4B2A-4838-93F9-D9F6A5F5EBFB_2%2FJVmsG5gbeSkqUr5rhV7sA9s780ZkBDy1riLroKNsZLwz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One major show stopper for many people is that it doesn’t build for Windows and Linux. I also feel it’s only just becoming a &lt;em&gt;real&lt;/em&gt; solution as of it's 3rd major release which adds much needed functionality. Functionality such as search and focus states weren't properly supported before so you'd have to write it yourself. There are also bugs that crop up and it's down to Apple's discretion as to when these get fixed.&lt;/p&gt;

&lt;p&gt;The community and packages surrounding SwiftUI tend to focus on mobile, however, there are still a reasonable amount of resources for MacOS. If you're interested, &lt;a href="https://developer.apple.com/tutorials/swiftui/creating-a-macos-app" rel="noopener noreferrer"&gt;take a look at this official tutorial for MacOS&lt;/a&gt; for getting started.&lt;/p&gt;

&lt;h4&gt;
  
  
  Positives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Easy to make native Mac apps that &lt;em&gt;look&lt;/em&gt; like Mac apps&lt;/li&gt;
&lt;li&gt;Lots of resources for learning that are applicable for iOS and MacOS&lt;/li&gt;
&lt;li&gt;Share code between iOS, tvOS, watchOS&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Negatives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;No build for Windows or Linux&lt;/li&gt;
&lt;li&gt;Bugs are fixed at the whim of Apple&lt;/li&gt;
&lt;li&gt;Only one major release per year with new functionality&lt;/li&gt;
&lt;li&gt;Closed source&lt;/li&gt;
&lt;li&gt;Only fairly recent MacOS versions support it and each of the previous versions of MacOS support fewer features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tauri
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F68B0A145-E79B-4948-B8BB-E9D03CEF204E_2%2FWkBtfxfH3bSyuWoTuD9O0tbdyG2RRlQcpreibmcz6Sgz%2FImage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2F68B0A145-E79B-4948-B8BB-E9D03CEF204E_2%2FWkBtfxfH3bSyuWoTuD9O0tbdyG2RRlQcpreibmcz6Sgz%2FImage.png" alt="Image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tauri is another fairly new library. It’s a web wrapper and you can use whichever web framework you prefer. There’s an officially supported plugin for Vue.JS, but it is simple enough to add your own. I've had it working with both Ember JS and Svelte.&lt;/p&gt;

&lt;p&gt;It’s first major difference from Electron is that it uses your Operating System’s web browser rather than bundling Chrome. This results in fairly tiny file sizes, but at the cost of having to debug issues on different platforms.&lt;/p&gt;

&lt;p&gt;The second major difference is that Tauri uses Rust. With Electron you pass messages from main and renderer with Node and Javascript, whereas with Tauri you pass events from the frontend and backend with Javascript and Rust, respectively.&lt;/p&gt;

&lt;p&gt;Here’s a snippet from the Tauri documentation of communicating between the two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getCurrent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WebviewWindow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tauri-apps/api/window&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// emit an event that are only visible to the current window&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCurrent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tauri is awesome!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// create a new webview window and emit an event only to that window&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebviewWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;window&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;webview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the payload type must implement `Serialize`.&lt;/span&gt;
&lt;span class="c1"&gt;// for global events, it also must implement `Clone`.&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Clone,&lt;/span&gt; &lt;span class="nd"&gt;serde::Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.setup&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// listen to the `event-name` (emitted on any window)&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.listen_global&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"got event-name with payload {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="nf"&gt;.payload&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="c1"&gt;// unlisten to the event using the `id` returned on the `listen_global` function&lt;/span&gt;
      &lt;span class="c1"&gt;// an `once_global` API is also exposed on the `App` struct&lt;/span&gt;
      &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.unlisten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// emit the `event-name` event to all webview windows on the frontend&lt;/span&gt;
      &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.emit_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Tauri is awesome!"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;generate_context!&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to run app"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve built and released one app with Tauri and it was fairly painless for a simple app. I used Svelte for the web framework and each installer came out at less than 5MB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FC06B5D4E-361E-47B4-BFBD-5EE4144BB008_2%2F4UPVE21KHfVURoUbWBvyPVcns6u8C4zZ3fjjn7gmSkoz%2FImage" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.craft.do%2Fuser%2Ffull%2F1151b1b2-72dd-5898-2b89-8130bb43a6e7%2Fdoc%2F3E58B53A-C376-4396-A034-62CC244259EE%2FC06B5D4E-361E-47B4-BFBD-5EE4144BB008_2%2F4UPVE21KHfVURoUbWBvyPVcns6u8C4zZ3fjjn7gmSkoz%2FImage" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For larger apps, I would most likely struggle to implement certain functionality. The getting started guides are easy enough to follow, but once I started trying to add more functionality I found the overall documentation lacking. There’s also fewer features than Electron which is to be expected since the platform is not as mature and the community not as large.&lt;/p&gt;

&lt;p&gt;It supports adding CLI’s to your app which I think is a very cool feature that’s not often built into GUI libraries. You can also embed external binaries which can be very useful if you need to use a command-line tool for functionality in your app. It also supports auto updating for each platform (With Linux supporting AppImage).&lt;/p&gt;

&lt;h4&gt;
  
  
  Positives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Supports auto updating on MacOS, Windows and Linux&lt;/li&gt;
&lt;li&gt;Build your own companion CLI&lt;/li&gt;
&lt;li&gt;Integrate external binaries&lt;/li&gt;
&lt;li&gt;Small distribution file sizes&lt;/li&gt;
&lt;li&gt;Use any frontend JS framework you prefer&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Negatives
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Fewer features than alternatives&lt;/li&gt;
&lt;li&gt;Small community and ecosystem&lt;/li&gt;
&lt;li&gt;Not yet at a stable release&lt;/li&gt;
&lt;li&gt;Different OS browsers can (and will) behave differently - extra testing required&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GUI Library Overview
&lt;/h2&gt;

&lt;p&gt;I thought it would be beneficial to have a casual overview of the differences between platforms, including differences in community size and support.&lt;/p&gt;

&lt;p&gt;Releases in the past 6 months gives some indication of activity on each project, and includes beta, dev, and RC releases. This information is taken from each project’s git repository and is checked between 1st July 2021 and 1st January 2022.&lt;/p&gt;

&lt;p&gt;As SwiftUI is not open source and other than at WWDC where major changes are announced, we do not get a run-down of changes between Xcode versions, it’s difficult to compare. We do know however that SwiftUI is backed by Apple and appears to be the recommended way of making apps for the Apple ecosystem moving forward.&lt;/p&gt;

&lt;p&gt;SwiftUI is also the only platform out of the list that does not support Windows/Linux. It does however have support for iOS, iPadOS, Apple Watch, and Apple TV. If you’re in the Apple ecosystem, it’s definitely something to consider.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework/Library&lt;/th&gt;
&lt;th&gt;Language(s)&lt;/th&gt;
&lt;th&gt;Native&lt;/th&gt;
&lt;th&gt;Platform Support&lt;/th&gt;
&lt;th&gt;Contributors&lt;/th&gt;
&lt;th&gt;Releases in past 6 months&lt;/th&gt;
&lt;th&gt;Initial release date&lt;/th&gt;
&lt;th&gt;Stable release?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compose&lt;/td&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;💻🪟🐧🤖&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;2nd April 2021&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;egui&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;💻🪟🐧&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;30th May 2020&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;td&gt;Javascript&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;💻🪟🐧&lt;/td&gt;
&lt;td&gt;1081&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;12 Aug 2013&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React Native for Windows&lt;/td&gt;
&lt;td&gt;Javascript/Typescript&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;💻🪟🤖📱&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;23 Jun 2020&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;Dart&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;💻🪟🐧🤖📱&lt;/td&gt;
&lt;td&gt;957&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;27th Feb 2018&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tauri&lt;/td&gt;
&lt;td&gt;Rust + Javascript&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;💻🪟🐧&lt;/td&gt;
&lt;td&gt;114&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;18th December 2019&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;p&gt;Not all frameworks have every feature. If you’re looking to make an application that relies on specific things such as webcam support then you’ll need to check if it works or you’ll have to code it yourself.&lt;/p&gt;

&lt;p&gt;Note that, my Google-foo may fail. I have tried looking through documentation and various resources for each library but unfortunately it’s not always easy to find if a solution exists.&lt;/p&gt;

&lt;p&gt;Additionally, these features may get added after this article is published, so do your own research, too!&lt;/p&gt;

&lt;p&gt;Here’s a key for the tables below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ - native/first party support&lt;/li&gt;
&lt;li&gt;📦 - support via external plugin&lt;/li&gt;
&lt;li&gt;🎓 - tutorial/community information available&lt;/li&gt;
&lt;li&gt;❓- Unknown (Most likely unavailable)&lt;/li&gt;
&lt;li&gt;❌ - unsupported/unavailable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For theming and light/dark mode I’ll be looking at native support for the Operating System’s features. Web wrappers also generally have features that you can use from the browser, e.g. webcam support via JS, which I mention in the table.&lt;/p&gt;

&lt;p&gt;Automatic updates for Linux are only available for Electron and Tauri via AppImage. Unfortunately most libraries don’t support over the air updates or only partially support it, and in this case you’ll have to either implement it yourself, or simply prompt the user to install the next update manually by checking a web-hook that you set up and manage.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework/Library&lt;/th&gt;
&lt;th&gt;Context Menus&lt;/th&gt;
&lt;th&gt;Window Menus&lt;/th&gt;
&lt;th&gt;Multiple Windows/Window Manipulation&lt;/th&gt;
&lt;th&gt;Webcam/Microphone&lt;/th&gt;
&lt;th&gt;Automatic updates&lt;/th&gt;
&lt;th&gt;Theming, Light and Dark mode&lt;/th&gt;
&lt;th&gt;Tray&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compose&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌ (&lt;a href="https://github.com/JetBrains/compose-jb/issues/1043" rel="noopener noreferrer"&gt;issue&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;🎓(&lt;a href="https://dev.to/tkuenneth/automatically-switch-to-dark-mode-and-back-in-compose-for-desktop-303l"&gt;link&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;egui&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (&lt;a href="https://docs.rs/egui/0.3.0/egui/menu/index.html" rel="noopener noreferrer"&gt;basic&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;❓(&lt;a href="https://github.com/emilk/egui/issues/552" rel="noopener noreferrer"&gt;issue)&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;🎓(&lt;a href="https://github.com/emilk/egui/issues/1001" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;td&gt;📦 (&lt;a href="https://www.npmjs.com/package/electron-context-menu" rel="noopener noreferrer"&gt;plugin&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (Via JS)&lt;/td&gt;
&lt;td&gt;💻🪟🐧&lt;/td&gt;
&lt;td&gt;✅ (&lt;a href="https://www.electronjs.org/docs/latest/tutorial/dark-mode" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;📦 (&lt;a href="https://pub.dev/packages/flutter_menu" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://bestofcpp.com/repo/lesnitsky-native_context_menu" rel="noopener noreferrer"&gt;2&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;📦 (p&lt;a href="https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar" rel="noopener noreferrer"&gt;lugin)&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;td&gt;🎓(&lt;a href="https://stackoverflow.com/a/69645100" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React Native for Windows&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;td&gt;Microsoft Store&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SwiftUI&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (Using AppKit)&lt;/td&gt;
&lt;td&gt;Mac App Store, &lt;a href="https://sparkle-project.org" rel="noopener noreferrer"&gt;Sparkle&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tauri&lt;/td&gt;
&lt;td&gt;❌ (JS library work around)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;(Via JS)&lt;/td&gt;
&lt;td&gt;💻🪟🐧&lt;/td&gt;
&lt;td&gt;✅ (Via CSS)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Accessibility
&lt;/h3&gt;

&lt;p&gt;There’s many different levels of accessibility so I thought it would be worth investigating.&lt;/p&gt;

&lt;p&gt;When looking at font size, I’m referring to ability to use the Operating System’s font scaling. Most tools are able to implement their own font scaling if they wanted to — or with a bit of additional code.&lt;/p&gt;

&lt;p&gt;Interestingly, I tried testing this with Compose on Windows and the font refused to scale up. egui and Flutter worked fine, and browser based libraries will use the web browsers native font scaling.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework/Library&lt;/th&gt;
&lt;th&gt;Voice over&lt;/th&gt;
&lt;th&gt;Keyboard shortcuts&lt;/th&gt;
&lt;th&gt;Tooltips&lt;/th&gt;
&lt;th&gt;OS font size scaling&lt;/th&gt;
&lt;th&gt;Tab focusing/cycling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compose&lt;/td&gt;
&lt;td&gt;✅ - Mac Only, Windows planned&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;egui&lt;/td&gt;
&lt;td&gt;❌ (i&lt;a href="https://github.com/emilk/egui/issues/167" rel="noopener noreferrer"&gt;ssue)&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;❌ (i&lt;a href="https://github.com/emilk/egui/issues/31" rel="noopener noreferrer"&gt;ssue)&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🎓 (&lt;a href="https://www.npmjs.com/package/electron-tooltip" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;✅ (Chromium handles this)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;❓(&lt;a href="https://docs.flutter.dev/development/accessibility-and-localization/accessibility" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;✅(link &lt;a href="https://docs.flutter.dev/development/ui/advanced/actions_and_shortcuts" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://wilsonwilson.dev/articles/keyboard-shortcuts-in-flutter/" rel="noopener noreferrer"&gt;2&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🎓 (&lt;a href="https://github.com/flutter/flutter/issues/55033" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React Native for Windows&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;td&gt;❌ (&lt;a href="https://github.com/microsoft/react-native-windows/issues/3450" rel="noopener noreferrer"&gt;issue&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SwiftUI&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (MacOS Montery+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tauri&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;td&gt;✅ (Via JS)&lt;/td&gt;
&lt;td&gt;✅(Via JS)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Final Recommendations
&lt;/h3&gt;

&lt;p&gt;When choosing a library for building a desktop app, I think you have to ask yourself which category your application falls into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Personal project to solve your own issue&lt;/li&gt;
&lt;li&gt;Small scope software with few updates or released as feature complete&lt;/li&gt;
&lt;li&gt;Projects targeting developers&lt;/li&gt;
&lt;li&gt;Product to be distributed and available to as many people as possible with frequent updates (E.g. a SaaS)&lt;/li&gt;
&lt;li&gt;Enterprise - stability and maintainability at utmost importance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For personal and feature complete software, I’d suggest going for the one that appeals the most to you, assuming it has the features you need.&lt;/p&gt;

&lt;p&gt;For most other projects, you're most likely going to want to have automatic updates available. That is, unless you want to respond to every support request with 'Can you update to the latest version please’.&lt;/p&gt;

&lt;p&gt;It's a real shame that it removes many of the otherwise great libraries from the running. If you can get away with it, you could instead implement a prompt that tells users to download a newer version manually when it’s available. Still, OTA updates are almost a requirement for desktop software today.&lt;/p&gt;

&lt;p&gt;There is also a niche for software that only targets Apple devices. Plenty of developers go this route, just take a look at &lt;a href="https://www.sketch.com" rel="noopener noreferrer"&gt;Sketch&lt;/a&gt;, &lt;a href="http://panic.com" rel="noopener noreferrer"&gt;Panic&lt;/a&gt;, &lt;a href="http://craft.do" rel="noopener noreferrer"&gt;Craft docs&lt;/a&gt;, as a few examples. It certainly simplifies development, and if you're already in the Apple ecosystem it's great to scratch your own itch. If this sounds like your situation then SwiftUI is a great choice.&lt;/p&gt;

&lt;p&gt;I really like all of these libraries, but Electron is the solution that's least likely to bite you with it's large community, ecosystem and feature set. That said, I'm eager to see the other solution grow in the future.&lt;/p&gt;

&lt;p&gt;If you have any thoughts or suggestions for tools I should check out. Please feel free to comment! You can reach me on &lt;a href="https://mastodon.technology/@mitchartemis/107656532224254703" rel="noopener noreferrer"&gt;Mastadon&lt;/a&gt;, &lt;a href="https://twitter.com/mitchartemis_v2/status/1484252370192187392" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://dev.to/mitchartemis/desktop-guis-for-web-developers-2i9d"&gt;Dev.to&lt;/a&gt;, &lt;a href="https://mitchartemis.dev/2022/01/20/new-article-this.html" rel="noopener noreferrer"&gt;Micro.blog&lt;/a&gt;, or &lt;a href="https://www.fullstackstanley.com/articles/desktop-guis-for-webdevelopers/" rel="noopener noreferrer"&gt;comment directly on the original article&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rust</category>
      <category>flutter</category>
      <category>vue</category>
    </item>
    <item>
      <title>Introducing Rsyncinator</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Wed, 23 Jun 2021 09:40:59 +0000</pubDate>
      <link>https://dev.to/mitchartemis/introducing-rsyncinator-3kcj</link>
      <guid>https://dev.to/mitchartemis/introducing-rsyncinator-3kcj</guid>
      <description>&lt;p&gt;In March last year, I released a web tool for building rsync commands via a GUI. I've now further developed it into it's own Mac app which is named Rsyncinator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nlAeoStD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ho6s5bumr7xxldqyqfxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nlAeoStD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ho6s5bumr7xxldqyqfxl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Mac app adds some niceties, such as being able to store and load commonly used rsync commands and using a convenient file picker for local paths. It also comes with all the goodness of a native Mac app:  It's fast, small, and has multi window/tab support.&lt;/p&gt;

&lt;p&gt;After I finished building it, I decided it needed a website of its own. So I've rewritten the old Rsync for Snipline website from the ground up. You can still use the web tool, although it's slightly different now (More inline with the Mac app).&lt;/p&gt;

&lt;p&gt;You can see the &lt;a href="https://rsyncinator.app"&gt;new website here&lt;/a&gt; and if you like the look if it, you can get the Mac app on the &lt;a href="https://apps.apple.com/gb/app/rsyncinator/id1569680330?mt=12"&gt;App Store&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Snipline 2 Progress Report May 2021</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Wed, 26 May 2021 15:11:45 +0000</pubDate>
      <link>https://dev.to/mitchartemis/snipline-2-progress-report-may-2021-2kdm</link>
      <guid>https://dev.to/mitchartemis/snipline-2-progress-report-may-2021-2kdm</guid>
      <description>&lt;p&gt;If you don’t know what Snipline is, here’s a brief run-down: Snipline started as a tool to scratch my own itch. I wanted an app that streamlined saving, searching and using shell commands.&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more, checkout the &lt;a href="https://snipline.io"&gt;home page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  UI Refresh
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wAoPQE5g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ci2r74bllmtn6h5wtsd6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wAoPQE5g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ci2r74bllmtn6h5wtsd6.png" alt="Version 1 vs Version 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most obvious change is the UI. The structure is very similar and yet very different at the same time. I've moved from Tailwind 1 to Tailwind 2, and with that I'm also using Tailwind UI for some of the elements. There's also some other noticeable changes like no more settings button, and the moved 'New Snippet' button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Farewell Command Bar, Hello Command Palette
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hmHMO6u1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vowws2iixe46g0uphjyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hmHMO6u1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vowws2iixe46g0uphjyz.png" alt="The command bar in v1 vs the command palette in v2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the UI Refresh screenshot, you may have noticed the command bar is no longer visible, and that's because it no longer exists. I want to move Snipline 2 to a &lt;em&gt;command palette&lt;/em&gt; style workflow. Popularised by apps such as Sublime Text 2 where you hit a keybind and you select an action.&lt;/p&gt;

&lt;p&gt;In v1 the flow was either&lt;/p&gt;

&lt;p&gt;a) Search snippet and click run/copy.&lt;/p&gt;

&lt;p&gt;b) Search and type the snippet number in the command bar (E.g. &lt;code&gt;:1&lt;/code&gt; to run, or &lt;code&gt;:e 1&lt;/code&gt; to edit etc ).&lt;/p&gt;

&lt;p&gt;In v2 the flow is&lt;/p&gt;

&lt;p&gt;a) Search snippet and click run/copy&lt;/p&gt;

&lt;p&gt;b) Search snippet, press &lt;code&gt;enter&lt;/code&gt; to open the command palette on the selected snippet, choose your action, e.g. &lt;code&gt;run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;c) &lt;code&gt;Ctrl/Command+enter&lt;/code&gt; to skip the palette and run the selected snippet.&lt;/p&gt;

&lt;p&gt;It's a big change which makes using Snipline more visual and app-like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dedicated preference window
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LUXLWbRQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0vk75i2n6ikf8cmssad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LUXLWbRQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0vk75i2n6ikf8cmssad.png" alt="v1 settings vs v2 preferences"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Snipline 1 the settings were done within the main app window. This worked fine, but there were two issues that bugged me.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There needed to be a button to open the settings within the main app which took up space.&lt;/li&gt;
&lt;li&gt;It's more difficult to scale out the settings within a potentially constrained size.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With Snipline 2 I'm pleased to show that the preferences are in their own window. This can be opened from the menu or by the Operating Systems conventional preference shortcut (e.g. in MacOS this is &lt;code&gt;⌘+,&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This means the icon is no longer required within the main app, which helps to make it look cleaner. More importantly, I can expand the preferences into different tabs like most applications do.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Features
&lt;/h2&gt;

&lt;p&gt;There are lots of small QoL improvements in this release, but here are some of the bigger ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom keybinds and Global shortcut!
&lt;/h3&gt;


    
        Your browser does not support the video tag.


&lt;p&gt;It's a common request for Snipline to have a global keybind and here it is. Personally, I have it set to &lt;code&gt;hyper+s&lt;/code&gt; but this is configurable to your own taste.&lt;/p&gt;

&lt;p&gt;Many of the navigational elements can be customised &lt;strong&gt;and&lt;/strong&gt; support chording. What is chording? Those familiar with Vim will know, instead of using a single combination of keys, you can press multiple keys in succession. A common one for Vim would be &lt;code&gt;g g&lt;/code&gt; to go to the bottom of the file.&lt;/p&gt;

&lt;p&gt;And if you use command/ctrl keys in your shortcuts you can use these without leaving the search bar!&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline mode
&lt;/h3&gt;

&lt;p&gt;See the 'Internal Updates' and 'Pricing' sections later in this post for more details on why I'm adding this.&lt;/p&gt;

&lt;h3&gt;
  
  
  New syntaxes
&lt;/h3&gt;

&lt;p&gt;I have not started implementing these yet so I can't make any promises, but there are three new syntaxes I'd like to add: One for replacing text with your current clipboard, some kind of confirm/if statement, and variable transforming (e.g. to camelCase, snake_case, etc).&lt;/p&gt;

&lt;h2&gt;
  
  
  Internal Updates
&lt;/h2&gt;

&lt;p&gt;In the beginning, the main reason I labelled this as 'Snipline 2' was because of all the internal updates I wanted to do. Most importantly, it's using Ember Octane and Electron 12, which I hope will make the app more performant. And perhaps more noticeably, the switch to Tailwind 2 and TailwindUI components.&lt;/p&gt;

&lt;p&gt;However, the biggest internal change is the fact it's storing snippets locally by default. This means no more signing in to your account or having an active internet connection.&lt;/p&gt;

&lt;p&gt;Since Snipline 2 will be local by default it means that Snipline CLI and Snipbar will not be compatible with it - at least initially. I may do an update for these, or possibly new apps. Time will tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;One of the biggest complaints I received from Snipline 1 was the fact it was subscription based. I &lt;em&gt;sort of&lt;/em&gt; addressed this by bringing in the 'Pay what you want' model, reducing the initial price down to $9/year. I can happily say this helped bring in more subscribers and as I've mentioned in previous blog posts, and I still get many people that go for the original price of $18/year which I really appreciate.&lt;/p&gt;

&lt;p&gt;I completely understand subscription fatigue, and I wanted to address this from the start with Snipline 2. So I'm pleased to announce that the base app, which is usable offline and has 90% of the features, will be a one time cost. Buying this will give you a license key which you can use on your devices for as long as you want.&lt;/p&gt;

&lt;p&gt;I plan to create two tiers of subscriptions: &lt;strong&gt;Pro&lt;/strong&gt; and &lt;strong&gt;Team&lt;/strong&gt; which have some additional &lt;em&gt;server&lt;/em&gt; based features.&lt;/p&gt;

&lt;p&gt;The Pro plan will include syncing between devices, Sharing Snippets via a web URL, and snippet revisions. A side effect of this plan is that you essentially have a back up of your snippets, too.&lt;/p&gt;

&lt;p&gt;The Team plan will include the Pro features but allow you to share snippets between teammates.&lt;/p&gt;

&lt;p&gt;The price of these are to be confirmed, but it's safe to say they're not going to be too different to the current price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade plan
&lt;/h2&gt;

&lt;p&gt;Chances are, if you're interested enough to read this far, you're probably a subscriber to Snipline 1.0, and you're wondering &lt;strong&gt;What about me?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My plan for active Snipline 1.0 users is to offer a free license for the app, and one year of the Pro plan. I'll also hopefully be giving beta invites for these users first!&lt;/p&gt;

&lt;p&gt;I've said in a previous blog post that Snipline 1 and Snipline 2 can be run side by side. This is still true, but they will not be synced with each other. I would encourage Snipline 1 migrants to import their existing Snippets into Snipline 2 and when they are ready move over to the new subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;You reached the end of the blog! Thanks for reading, feel free to reach out to me on &lt;a href="https://twitter.com/mitchartemis_v2"&gt;Twitter&lt;/a&gt; if you have any questions.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building small desktop apps with Ember.js and Tauri</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Mon, 24 May 2021 13:04:23 +0000</pubDate>
      <link>https://dev.to/mitchartemis/building-small-desktop-apps-with-ember-js-and-tauri-3o28</link>
      <guid>https://dev.to/mitchartemis/building-small-desktop-apps-with-ember-js-and-tauri-3o28</guid>
      <description>&lt;p&gt;I recently played around with Tauri, a toolkit for development desktop apps with web technologies. Here's how I got it working with an Ember.js application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Ember?
&lt;/h2&gt;

&lt;p&gt;Ember.js is a frontend framework similar to React and Vue JS. I used it to build my app &lt;a href="https://snipline.io" rel="noopener noreferrer"&gt;Snipline&lt;/a&gt;, and it's also used for websites like Intercom and LinkedIn. It has a 'convention over configuration' approach similar to Ruby on Rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Tauri?
&lt;/h2&gt;

&lt;p&gt;Tauri is a library for making desktop applications with web technologies. Similar to Electron with a few key differences: &lt;/p&gt;

&lt;p&gt;1) It's built in Rust rather than Javascript. &lt;/p&gt;

&lt;p&gt;2) It uses your operating system's native web browser rather than bundling Chrome which resulting in quite tiny applications—at least compared to Electron!&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and development
&lt;/h2&gt;

&lt;p&gt;Here are the commands I ran for a simple Ember app to test routing with Ember and Tauri. For reference, I'm using Node. 14.17.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Ember
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; ember-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ember new tauri-test &lt;span class="nt"&gt;--lang&lt;/span&gt; en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ember g route index
ember g route from-ember
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ember serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I edited the two generated templates, &lt;code&gt;app/templates/index.hbs&lt;/code&gt; and &lt;code&gt;app/templates/from-ember.hbs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tauri&lt;/span&gt; &lt;span class="err"&gt;😄&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkTo&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from-ember&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Click&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/LinkTo&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FromEmber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt; &lt;span class="nx"&gt;Ember&lt;/span&gt; &lt;span class="err"&gt;🧡&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkTo&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Back&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/LinkTo&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's enough to get started and test that routing works within the app. Now let's get to the good stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Tauri
&lt;/h3&gt;

&lt;p&gt;First, &lt;a href="https://tauri.studio/en/docs/getting-started/intro" rel="noopener noreferrer"&gt;follow the set up guide for your OS in the Tauri documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After this, it's a matter of adding it to your ember project - &lt;a href="https://tauri.studio/en/docs/usage/development/integration" rel="noopener noreferrer"&gt;See the integration documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is what I did to get it working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @tauri-apps/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Add the `tauri` command to your `package.json`
{
  // This content is just a sample
  "scripts": {
    "tauri": "tauri"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run through the initialisation process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run tauri init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted, make sure that you set the development server to &lt;code&gt;http://localhost:4200&lt;/code&gt; and the location of the files (relative to &lt;code&gt;src-tauri&lt;/code&gt;) to &lt;code&gt;../dist&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then it's just a matter of running the development subcommand (Make sure your Ember server is still up, too).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run tauri dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! It works even with hot reloading!&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging
&lt;/h2&gt;

&lt;p&gt;With development out of the way, here's how to package the app for distribution. I won't be looking at auto-updates in this guide, but &lt;a href="https://tauri.studio/en/docs/usage/guides/updater" rel="noopener noreferrer"&gt;Tauri does have support for this&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ember build &lt;span class="nt"&gt;--environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
npm run tauri build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On MacOS installer &lt;code&gt;.dmg&lt;/code&gt; file came out at 5.4MB and the &lt;code&gt;.app&lt;/code&gt; file  12.4MB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1621767450348%2FhYnW6LQ04.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1621767450348%2FhYnW6LQ04.gif" alt="Tauri running the Ember.js app in MacOS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Windows, the generated MSI installer came to 4.9MB and the executable 8.9MB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkubj34m4i8jagvfz38id.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkubj34m4i8jagvfz38id.png" alt="Tauri running the Ember.js app in Windows"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Communicating between Rust and Ember
&lt;/h2&gt;

&lt;p&gt;Taking this one step further, I thought I'd test a simple ping/pong example of communicating between Ember and Rust. For more information &lt;a href="https://tauri.studio/en/docs/usage/guides/command" rel="noopener noreferrer"&gt;check the Tauri docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following code allows for Ember to pass a string to Rust, Rust checks the value and toggles between the text 'Ping' and 'Pong'. In Ember, I've added a button that displays the response text and updates it on click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src-tauri/src/main.rs&lt;/span&gt;
&lt;span class="nd"&gt;#![cfg_attr(&lt;/span&gt;
  &lt;span class="nd"&gt;all(not(debug_assertions),&lt;/span&gt; &lt;span class="nd"&gt;target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"windows"&lt;/span&gt;&lt;span class="nd"&gt;),&lt;/span&gt;
  &lt;span class="nd"&gt;windows_subsystem&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"windows"&lt;/span&gt;
&lt;span class="nd"&gt;)]&lt;/span&gt;

&lt;span class="c1"&gt;// Add a new function that takes a string and returns a string&lt;/span&gt;
&lt;span class="nd"&gt;#[tauri::command]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;my_custom_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Depending on what we receive from Ember we toggle the response&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_text&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Ping"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"Pong!"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"Ping"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Add the custom command so that the frontend can invoke it&lt;/span&gt;
  &lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.invoke_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;generate_handler!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;my_custom_command&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;generate_context!&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error while running tauri application"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/controllers/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ember/controller&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ember/object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tracked&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@glimmer/tracking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tauri-apps/api/tauri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IndexController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Set the default button text&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;tracked&lt;/span&gt; &lt;span class="nx"&gt;buttonText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ping&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="c1"&gt;// Create an action that will be attached to a button in the template&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;action&lt;/span&gt;
    &lt;span class="nf"&gt;checkRust&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Invoke the Rust command and update the button text to the response&lt;/span&gt;
        &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my_custom_command&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonText&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the updated &lt;code&gt;app/templates/index.hbs&lt;/code&gt; template file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tauri&lt;/span&gt; &lt;span class="err"&gt;😄&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkTo&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from-ember&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Click&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/LinkTo&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkRust&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonText&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuc4ziujfzlb3nzmrh3f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuc4ziujfzlb3nzmrh3f.gif" alt="Tauri and Ember.js communicating with each other"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty cool! I'm excited to see where Tauri goes, and to see its plugin ecosystem grow. Should I try building a full project in it or write some more tutorials using both technologies? Let me know in the comments!&lt;/p&gt;

</description>
      <category>ember</category>
      <category>rust</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Updated my blog to Zola and TailwindUI</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Fri, 21 May 2021 07:56:45 +0000</pubDate>
      <link>https://dev.to/mitchartemis/updated-my-blog-to-zola-and-tailwindui-3pc3</link>
      <guid>https://dev.to/mitchartemis/updated-my-blog-to-zola-and-tailwindui-3pc3</guid>
      <description>&lt;p&gt;As is customary for web developer blogs, I spend more time building the website than I do blogging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zvU0uYuC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/352wm82kpbgnuxr9ak9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zvU0uYuC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/352wm82kpbgnuxr9ak9k.png" alt="The new website available in light and dark mode"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.fullstackstanley.com"&gt;Link to the website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That said, there were a few issues with my previous site that made me want a refresh.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comments weren't always loading.&lt;/li&gt;
&lt;li&gt;No call to actions or even social links!&lt;/li&gt;
&lt;li&gt;Formatting wasn't always quite right, especially with syntax highlighting.&lt;/li&gt;
&lt;li&gt;Ability to link up series' of articles (Like dev.to).&lt;/li&gt;
&lt;li&gt;Add warning disclaimer to old posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u-kFGdQW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo7woqciflt4rakdz0ek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u-kFGdQW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo7woqciflt4rakdz0ek.png" alt="A preview of how old posts and series' look"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyway, I've rebuilt the site with &lt;a href="https://getzola.org"&gt;Zola&lt;/a&gt; and &lt;a href="https://tailwindui.com"&gt;TailwindUI&lt;/a&gt; and I'm pretty pleased with it.&lt;/p&gt;

&lt;p&gt;I'm promoting my apps a bit more with it and my social media presence.&lt;/p&gt;

&lt;p&gt;As a bonus, it has a native dark mode!&lt;/p&gt;

&lt;h2&gt;
  
  
  The old website
&lt;/h2&gt;

&lt;p&gt;Just for kicks, here's what the old website looked like. It was made with a template and used Nuxt.js + CraftCMS for the headless CMS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jDzdW3Ky--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdlmq19ptez11b8jt7q1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jDzdW3Ky--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdlmq19ptez11b8jt7q1.png" alt="The previous website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the first website! This one was built with Middleman, a Ruby static site generator. Back in 2013.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZZYecj_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6oh70lxtvl7f7atxlfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0ZZYecj_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6oh70lxtvl7f7atxlfr.png" alt="The first iteration of Full Stack Stanley"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Flutter vs Jetpack Compose for Desktop?</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Mon, 26 Apr 2021 13:53:25 +0000</pubDate>
      <link>https://dev.to/mitchartemis/flutter-vs-jetpack-compose-for-desktop-4ief</link>
      <guid>https://dev.to/mitchartemis/flutter-vs-jetpack-compose-for-desktop-4ief</guid>
      <description>&lt;p&gt;I've recently been researching which of these technologies to use for making desktop apps for Windows, Linux, and as a bonus, possibly mobile apps for Android.&lt;/p&gt;

&lt;p&gt;For context, I 'm primarily a web developer but very happy with using SwiftUI for iOS/iPadOS/MacOS for side projects. What I'm looking for is something to supplement this in other ecosystems.&lt;/p&gt;

&lt;p&gt;So far I've installed both Flutter and Compose and tried some beginner tutorials. My first thoughts are:&lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter Pros
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not restricted to a specific IDE.&lt;/li&gt;
&lt;li&gt;You can use Flutter cli.&lt;/li&gt;
&lt;li&gt;Getting Started guide was easy to follow. Even though it was for Android it worked well for Windows.&lt;/li&gt;
&lt;li&gt;Great support for Linux - with Ubuntu using it for software going forward.&lt;/li&gt;
&lt;li&gt;Lots of plugins, resources, and a big community.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Flutter Cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;IMO - Flutter seems less like SwiftUI than Compose - More verbose?&lt;/li&gt;
&lt;li&gt;Will desktop apps look like Android apps wrapped in a window? I see there are 'Yaru' style widgets for Ubuntu, but can't see anything for Windows.&lt;/li&gt;
&lt;li&gt;Desktop plugin ecosystem is more limited than Android.&lt;/li&gt;
&lt;li&gt;Has some edge case issues which make it a non-starter for certain projects. E.g. &lt;a href="https://github.com/flutter/flutter/issues/23913"&gt;#23913&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compose Pros
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I like the syntax - feels very similar to SwiftUI.&lt;/li&gt;
&lt;li&gt;Convenient previews in the IDE.&lt;/li&gt;
&lt;li&gt;Appears to a great, upcoming solution for Android.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compose Cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Currently has to use Android Studio Canary to make an Empty Compose for Desktop project.&lt;/li&gt;
&lt;li&gt;Code appears to differ more for each Ecosystem (Desktop vs Android). I tried following the beginner tutorial for Android but targeting Windows (Like I did with Flutter) and could not get this to work.&lt;/li&gt;
&lt;li&gt;Fewer resources - especially for Desktop development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These lists look very unbalanced in Flutters favour, but as a newbie to both ecosystems I'm wondering if I'm missing something. Part of me is very tempted by Compose, but as someone not too familiar with Android Development I feel it would be more of an uphill battle.&lt;/p&gt;

&lt;p&gt;I'm very keen to know what other people think about these two solutions, or if there is anything else out there that people are betting on!&lt;/p&gt;

</description>
      <category>question</category>
      <category>discuss</category>
      <category>flutter</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>Snipline: Year 2 in Review</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Wed, 10 Feb 2021 13:58:41 +0000</pubDate>
      <link>https://dev.to/mitchartemis/snipline-year-2-in-review-4khn</link>
      <guid>https://dev.to/mitchartemis/snipline-year-2-in-review-4khn</guid>
      <description>&lt;p&gt;This is the second annual review of &lt;a href="https://snipline.io"&gt;Snipline&lt;/a&gt;. The shell command bookmarker app.&lt;/p&gt;

&lt;p&gt;If you don't know what Snipline is, here's a brief run-down: Snipline started as a tool to scratch my own itch. I wanted an app that streamlined saving, searching and using shell commands.&lt;/p&gt;

&lt;p&gt;If you're interested in learning more, checkout the &lt;a href="https://snipline.io"&gt;home page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7o5aBZx_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gchob3mv6u3qf6nfbvda.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7o5aBZx_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gchob3mv6u3qf6nfbvda.png" alt="How Snipline Desktop currently looks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Notable Product Updates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Desktop
&lt;/h3&gt;

&lt;p&gt;Since last February the desktop app has added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 'Show' view.&lt;/li&gt;
&lt;li&gt;Improvements to the multi-choice syntax.&lt;/li&gt;
&lt;li&gt;Various bug fixes.&lt;/li&gt;
&lt;li&gt;1.0 release!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This seems small, but much of the desktop work is going on with the next big release of Snipline (version 2) which I'll speak about later in this post.&lt;/p&gt;

&lt;p&gt;More importantly, the app finally hit 1.0! Personally, I think i kept it as 0.x for too long, giving the impression that the software was not production ready. In reality it's been pretty stable for quite some time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snipline CLI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m7v2Orgm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qpobkkyweche290tzki2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m7v2Orgm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qpobkkyweche290tzki2.png" alt="Searching in Snipline CLI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The open source sidekick for Snipline has received a few updates this year, including support for building with Docker, allowing comma's in multi-choice parameters, and adding password authentication.&lt;/p&gt;

&lt;p&gt;The search functionality in Snipline CLI had a major update just before last year's review, and included a fancy terminal UI which was very interesting to build as I had no prior experience with NCurses. It certainly was an eye-opener!&lt;/p&gt;

&lt;h3&gt;
  
  
  Snipbar
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xcTorvan--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/csrikbtvzkmcv4nyqb41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xcTorvan--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/csrikbtvzkmcv4nyqb41.png" alt="Searching in Snipbar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The MacOS menu bar app is in a great place as of right now. I recently updated it with fixes for multi-choice comma support, and allowing multi-choice parameters to be re-used, bringing it inline with the Desktop app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other items
&lt;/h3&gt;

&lt;p&gt;There's been a lot of behind the scenes work going on. Including preparing for password based log-ins rather than token-based login.&lt;/p&gt;

&lt;p&gt;Emails switched from Sendgrid to Postmark as there were a lot of deliverability issues with Sendgrid. So far there has been no complaints since switching.&lt;/p&gt;

&lt;p&gt;I released &lt;a href="https://rsync.snipline.io/"&gt;Rsync Command Generator&lt;/a&gt;, a free tool for generating rsync commands. This tool receives a surprising amount of visits, to the point where I'm very tempted to make a standalone desktop app for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I92w5DIy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3g0bs5gnandaxbknzqge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I92w5DIy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3g0bs5gnandaxbknzqge.png" alt="Rsync Command Generator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also migrated the blog to Hugo. I was having a lot of issues with Middleman, my previous preferred static site generator, and Hugo seems to work well enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking back at last years review
&lt;/h2&gt;

&lt;p&gt;Last year I wrote about &lt;em&gt;getting the pricing right&lt;/em&gt;. Not every developer wanted to pay the $18/year subscription for Snipline. The pricing now has a 'Pay what you can' structure. I'm very happy to say that subscriptions are up significantly from last year. The majority of people have opted for the $9 plan, and I'm pleasantly surprised to see that there's been several $18 subscribers, too!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next for Snipline?
&lt;/h2&gt;

&lt;p&gt;First on the agenda is adding the Snipline CLI multi-choice reusable parameter fix, as well as updating it for the latest Crystal-lang release.&lt;/p&gt;

&lt;p&gt;I'm hoping that Snipbar will be the next app to support password based logins.&lt;/p&gt;

&lt;p&gt;Finally, there's Snipline 2...&lt;/p&gt;

&lt;h3&gt;
  
  
  Snipline 2
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nQmBo2iH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3xkmeux5l5ov715ag1d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nQmBo2iH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3xkmeux5l5ov715ag1d6.png" alt="A comparison of Snipline 1 vs an early prototype of 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've mentioned in a &lt;a href="https://blog.snipline.io/december-2020-update-snipline-2-preview-subscriber-update-and-ember-interview/"&gt;previous blog post&lt;/a&gt; that I'm very much focused on Snipline 2 at the moment. &lt;/p&gt;

&lt;p&gt;What is Snipline 2? Many of Snipline Desktop's dependencies are starting to show their age now, and although I could update them in Snipline 1, I want to make some changes to the design and functionality.&lt;/p&gt;

&lt;p&gt;Most importantly, I don't want to force users to use version 2 if they prefer version 1.&lt;/p&gt;

&lt;p&gt;My plan is to allow subscribers to continue using version 1 if they prefer it. Hopefully, they will see the benefits of version 2 and switch to it. There will be no additional cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Items that didn't make it in this year
&lt;/h2&gt;

&lt;p&gt;A few things I wanted to achieve in the 2nd year of Snipline which didn't make the cut:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client-side encryption&lt;/li&gt;
&lt;li&gt;Publishing an API&lt;/li&gt;
&lt;li&gt;iOS App&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are still very much on my mind. Client-side encryption is tricky for a few reasons, picking the right encryption, and having it available in Javascript/Crystal and Swift. More importantly, how to enable it (per snippet or per account?).&lt;/p&gt;

&lt;p&gt;I want to hold off on releasing a public API until the password based auth is finalised.&lt;/p&gt;

&lt;p&gt;The iOS app is a big task, and I feel I should focus on the new desktop app first. However, I recently released an app in SwiftUI which was a really pleasant experience. I would really like to do this for Snipline if there's interested in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you
&lt;/h2&gt;

&lt;p&gt;That's all for now. For all the Subscribers and trial users, thank you so much for using my little app. Many of you have reached out with bug reports and queries, and you've all honestly been so helpful and understanding. I hope that you will continue to use Snipline for the foreseeable future!&lt;/p&gt;

</description>
      <category>tooling</category>
    </item>
    <item>
      <title>My first (and worst) Rust program</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Mon, 09 Nov 2020 15:46:02 +0000</pubDate>
      <link>https://dev.to/mitchartemis/my-first-and-worst-rust-program-3jed</link>
      <guid>https://dev.to/mitchartemis/my-first-and-worst-rust-program-3jed</guid>
      <description>&lt;p&gt;I've been looking for an excuse to try out Rust for a while and decided to write a simple CLI to help split grocery costs between my partner and myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current Solution
&lt;/h2&gt;

&lt;p&gt;We currently split grocery costs in 3 ways, she pays for her items, I pay for mine, and the food we share we split the cost fifty/fifty. &lt;/p&gt;

&lt;p&gt;I receive an email receipt from our grocery store. I then enter the costs into &lt;a href="https://soulver.app/"&gt;Soulver&lt;/a&gt; to do some Maths and figure out how much we each owe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The current solution is slow and prone to human error (I get distracted). If I miss an item, add an item twice, or type in the wrong price, then one of us could end up owing too much or too little.&lt;/p&gt;

&lt;p&gt;Although we share some food, one of us often eats more than the other of &lt;em&gt;some&lt;/em&gt; products. In this situation the fifty fifty split is less fair.&lt;/p&gt;

&lt;p&gt;Trying to split these items more further would make the calculation more tedious and error prone.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Rust Solution
&lt;/h2&gt;

&lt;p&gt;Since I receive an email with the receipt I can take the text and run it through a script quite easily.&lt;/p&gt;

&lt;p&gt;The below Rust script, although terrible, seems to do the job just fine.&lt;/p&gt;

&lt;p&gt;It takes a file and runs through each line. Splits the cost by looking for the £ sign on each line. Then asks who the item is for. Now with the added benefit of being able to do a 25/75 split for items that are used more by one person than the other. It also keeps a running total for each person which is a nice bonus.&lt;/p&gt;

&lt;p&gt;Here's an example receipt. This is pasted directly from an email and very little is changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You ordered 1 X HECK 10 Meat-Free Magic 300g    £1.75
We sent 1 X HECK 10 Vegan Italia Chipolatas 300g    £1.75

Your order

Fridge  Quantity    Price
Curly Kale 150g 1   £0.50
Greek Style Yogurt 500g 1   £0.75
Soft Cheese 180g    1   £1.95
Fresh Whole Milk 1l 2   £2.80
Ready Rolled Filo Pastry 200g   1   £1.30
Extra Mature Cheddar Cheese 400g    1   £2.00
Fresh   Quantity    Price
Sweetclems 600g 1   £1.29
Freezer Quantity    Price
Frozen Strawberries 350g    1   £1.75
Vegetarian 2 Cheese &amp;amp; Spring Onion Crispbakes 280g  1   £1.50
Straight Cut Chips 1.5kg    1   £2.00
Groceries, Health &amp;amp; Beauty and Household Items  Quantity    Price
Green Beans 240g    1   £0.90
Thick Bleach Citrus Burst 750ml 2   £0.78
Corn Flakes 450g    1   £1.89
Wholewheat Penne 500g   1   £0.53
Maple Syrup 250g    1   £4.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the Rust code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BufRead&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;read_input&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;colored&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no pattern given"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Consumes the iterator, returns an (Optional) String&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="nf"&gt;.starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You ordered"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c"&gt;// do nothing&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="nf"&gt;.starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"We sent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;item_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="nf"&gt;.replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"We sent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item_text&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"£"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one_item_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two_item_cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                        &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;one_item_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;two_item_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="nf"&gt;running_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;one_pays&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;two_pays&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="nf"&gt;.ends_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Price"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c"&gt;// do nothing&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"£"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;one_item_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two_item_cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;one_item_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;two_item_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="nf"&gt;running_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;one_pays&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;two_pays&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;read_lines&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Lines&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BufReader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;File&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;BufReader&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.lines&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Who pays for {}? (£{})"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="nf"&gt;.bold&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"m/k/b/m3/k3/ignore: "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You said {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="nf"&gt;.green&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"m"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one pays an additional £{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"k"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"two pays an additional £{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"b"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;split_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Splitting cost: one pays £{}, two pays £{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"m3"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;split_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Splitting cost: one pays £{}, two pays £{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"k3"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;split_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Splitting cost: one pays £{}, two pays £{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;split_cost&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="mi"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid answer"&lt;/span&gt;&lt;span class="nf"&gt;.red&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.bold&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Press 'i' to ignore"&lt;/span&gt;&lt;span class="nf"&gt;.green&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;one_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one_pays&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;running_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one_pays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two_pays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Running Total"&lt;/span&gt;&lt;span class="nf"&gt;.bold&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-------------"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one: £{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;one_pays&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"two: £{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;two_pays&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-------------"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the Cargo.toml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cost-splitter"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["Mitch"]&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2018"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;read_input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8"&lt;/span&gt;
&lt;span class="py"&gt;colored&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;
&lt;span class="py"&gt;currency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"~0.4.0"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you have it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GJo7xmI4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tiogpcy9pttr7anx9lhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GJo7xmI4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tiogpcy9pttr7anx9lhm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The Dependencies
&lt;/h1&gt;

&lt;p&gt;I used three crates for this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://docs.rs/read_input"&gt;Read Input&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;From what I can tell there's no easy way to read from stdin with Rust out of the box. This crate makes it really easy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"m/k/b/m3/k3/ignore: "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://docs.rs/colored"&gt;Colored&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I added a bit of text formatting and colour with this crate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{} {} {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Text"&lt;/span&gt;&lt;span class="nf"&gt;.green&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Text 2"&lt;/span&gt;&lt;span class="nf"&gt;.green&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.bold&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Text 3"&lt;/span&gt;&lt;span class="nf"&gt;.green&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://docs.rs/currency"&gt;Currency&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I used this crate for handling the maths and formatting the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Currency&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split_row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Overall experience with Rust so far
&lt;/h1&gt;

&lt;p&gt;My experience developing this was pretty enjoyable. I was able to get a workable executable quite quickly with the help of the Rust compiler.&lt;/p&gt;

&lt;p&gt;I did, however, struggle with a few items.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Using Color with the Currency crate. I gave up on this in the end.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Strings are quite confusing. Not so much ownership which is also a new concept to me, but the various types of strings and when to use them. I tried to create an array of ignored items/lines to output at the end of the execution but couldn't get it working. Hopefully, with experience I'll become more familiar with strings and how to handle this.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What next?
&lt;/h1&gt;

&lt;p&gt;Aside from working on another Rust application I'm considering comparing this development experience with Go or Crystal. Go has piqued my interest—although I have no experience with it. I've used Crystal quite a lot and I suspect writing this application would be trivial in it. &lt;/p&gt;

&lt;p&gt;Let me know if you'd be interested in hearing about me port the application to either of these two languages!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My experience being suspended on Twitter</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Mon, 22 Jun 2020 08:49:43 +0000</pubDate>
      <link>https://dev.to/mitchartemis/my-experience-being-suspended-on-twitter-4a4f</link>
      <guid>https://dev.to/mitchartemis/my-experience-being-suspended-on-twitter-4a4f</guid>
      <description>&lt;p&gt;About two months ago I opened Twitter to find a modal message announcing to me that my account had been suspended. I initially thought, that's strange? Is this a bug? What could I possibly have done wrong? Twitter didn't feel it was necessary to give me any details. I was in the dark.&lt;/p&gt;

&lt;p&gt;I waited a few weeks. Naively thinking this issue would resolve itself—that it was just a mistake. I could still view tweets from people—although depending on which Twitter client I used I sometimes only saw the last tweets before my suspension took place.&lt;/p&gt;

&lt;p&gt;Here are some of other things I've noticed about being suspended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You cannot download your data.&lt;/li&gt;
&lt;li&gt;The suspension modal will popup almost randomly when browing the site.&lt;/li&gt;
&lt;li&gt;Notifications will stick. If someone tags you in a tweet (which is still possible) you'll be stuck with this notification forever.&lt;/li&gt;
&lt;li&gt;Follower and Following counts reset to zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, I caved in and contacted Twitter support. According to their support website it would take 1-2 weeks for a response. I waited patiently. After three weeks I touched base as I thought my ticket was taking quite long to get a response. It was an additional 4 weeks before I finally received one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hello,&lt;/p&gt;

&lt;p&gt;We’re writing to let you know that your account has been suspended—and will remain suspended—due to multiple or severe violations of our &lt;a href="https://help.twitter.com/rules-and-policies/platform-manipulation"&gt;platform manipulation rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please do not reply to this message as this email address is not monitored.&lt;/p&gt;

&lt;p&gt;Thanks,&lt;br&gt;
Twitter&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My heart sank a little. &lt;em&gt;will remain suspended&lt;/em&gt;. &lt;em&gt;severe violations&lt;/em&gt;. I've had this account for close to 10 years and there's a lot of history and contacts that I no longer have access to.&lt;/p&gt;

&lt;p&gt;After reading the platform manipulation rules I tried to put together what I had done wrong. &lt;/p&gt;

&lt;p&gt;I believe because I retweet my side project's tweets this must count as platform manipulation. To be honest, the thought of retweeting my other account as being against the ToS had not occurred to me. I only have myself to blame for not knowing this so I'm not angry about it. I do wish there was a warning system in place rather than going straight to permanent suspension, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  My social media moving forward
&lt;/h2&gt;

&lt;p&gt;Reflecting on this experience I think it's more important than ever to control my data. Not just on Twitter but on all social media. Twitter, Facebook, Instagram, etc should be a tool to share thoughts and experiences but not the soul source of them.&lt;/p&gt;

&lt;p&gt;I have created a new Twitter account (&lt;a href="https://twitter.com/mitchartemis_v2"&gt;@mitchartemis_v2&lt;/a&gt;) but I plan to be more purposeful with this one—to link to my own platforms, be it this blog or others.&lt;/p&gt;

&lt;p&gt;Then there's Mastodon which I want to use more of instead of Twitter. I currently have accounts on multiple Mastodon instances and to be honest, I'm not sure whether I should be focusing on using one account or sharing posts on individual nodes related to each of my interests. For example, posting my photography on photography.social, and my programming stuff on ruby.social. Unfortunately I feel this quandary is preventing me from posting anything at all.&lt;/p&gt;

&lt;p&gt;That's it for now. Just remember social media platforms control what happens with your data. If that data is important to you then make sure to have a back-up.&lt;/p&gt;

&lt;p&gt;Cross-posted from &lt;a href="https://www.mitchartemis.dev/posts/getting-suspended-on-twitter-and-my-social-media-presence-moving-forward/"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>How to deploy a Crystal Lucky application with Dokku on Ubuntu 18.04</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Wed, 20 May 2020 11:33:11 +0000</pubDate>
      <link>https://dev.to/mitchartemis/how-to-deploy-a-crystal-lucky-application-with-dokku-on-ubuntu-18-04-2dmj</link>
      <guid>https://dev.to/mitchartemis/how-to-deploy-a-crystal-lucky-application-with-dokku-on-ubuntu-18-04-2dmj</guid>
      <description>&lt;p&gt;&lt;strong&gt;UPDATE: A slightly modified version of this article is now available on the official docs! &lt;a href="https://luckyframework.org/guides/deploying/dokku"&gt;Check them out here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This tutorial will show how to set up an app written in the Crystal web framework &lt;a href="https://luckyframework.org"&gt;Lucky&lt;/a&gt; with Dokku on Ubuntu 18.04. &lt;/p&gt;

&lt;p&gt;You can run this setup on most low-end boxes. E.g. a $5 Digital Ocean droplet, or in my case, a $2.50 &lt;a href="https://www.hetzner.de/cloud"&gt;Hetzner Cloud Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the end of the tutorial you'll know how to deploy your own Lucky apps and be able to manage them easily all on one server.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Lucky?
&lt;/h3&gt;

&lt;p&gt;Lucky is a web framework written in &lt;a href="https://crystal-lang.org/"&gt;Crystal&lt;/a&gt;. Crystal is a programming language with syntax inspired by Ruby but with speeds similar to that of C.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Dokku?
&lt;/h3&gt;

&lt;p&gt;Dokku is a small PaaS service powered by Docker that you can run on your own servers. Think of it as a self-hosted command-line &lt;a href="https://www.heroku.com/"&gt;Heroku&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Crystal 0.34.0 installed locally&lt;/li&gt;
&lt;li&gt;A fresh Lucky application installed and running locally.&lt;/li&gt;
&lt;li&gt;Basic knowledge of Git.&lt;/li&gt;
&lt;li&gt;A server with &lt;a href="http://dokku.viewdocs.io/dokku/getting-started/installation/#installing-the-latest-stable-version"&gt;Dokku Installed&lt;/a&gt; using Ubuntu 18.04.&lt;/li&gt;
&lt;li&gt;A domain or subdomain to point to the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll need a Lucky app to deploy. If you don't have one then no problem! A fresh Lucky app comes with several goodies out of the box, a home page and an authentication system linked to a database. This is more than enough to work with. See &lt;a href="https://luckyframework.org/guides/getting-started/installing"&gt;Getting Started&lt;/a&gt; and &lt;a href="https://luckyframework.org/guides/getting-started/starting-project"&gt;Starting a Lucky Project&lt;/a&gt;. Make sure you have the project commited to git and ready for the first deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Setting up the Dokku configuration
&lt;/h2&gt;

&lt;p&gt;SSH into your server to get it ready for deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh root@ip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the following commands replace &lt;code&gt;app.example.com&lt;/code&gt; with your domain or subdomain. Replace &lt;code&gt;exampledb&lt;/code&gt; with your preferred database name.&lt;/p&gt;

&lt;p&gt;This will create the initial app container in Dokku, create the database, and link them both together. The last three lines set environment variables for the Lucky app. The first will tell Lucky to run the app in &lt;code&gt;production&lt;/code&gt; mode, the second will tell Lucky what the app URL is, and the third will tell the app to run on port 5000 which Dokku will connect with Nginx to expose to the world.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku apps:create app.example.com
dokku postgres:create exampledb
dokku postgres:link exampledb app.example.com
dokku config:set app.example.com LUCKY_ENV=production
dokku config:set app.example.com APP_DOMAIN=app.example.com
dokku config:set app.example.com PORT=5000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There's a few more configuration details that need to be set. First, you'll need to tell Lucky the database details that are set in Dokku. Run the below command to get the database URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku postgres:info exampledb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Set the &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable from the above output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set app.example.com DATABASE_URL=postgres://...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, Lucky needs a secret key environment variable. In your app directory on your local machine run the following command which will generate a secure key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lucky gen.secret_key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then, back on your remote Dokku server add it to the &lt;code&gt;SECRET_KEY_BASE&lt;/code&gt; environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set app.example.com SECRET_KEY_BASE=...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you're planning to send emails through the app you'll also need to set &lt;code&gt;SEND_GRID_KEY&lt;/code&gt; key, otherwise, change &lt;code&gt;config/email.cr&lt;/code&gt; to use &lt;code&gt;Carbon::DevAdapter.new&lt;/code&gt; in production (and make sure to commit!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set app.example.com SEND_GRID_KEY=...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Your server is almost ready for deployment. If you're using a server with 2GB of RAM or less, you'll need to set up a Swap. Without it the deployment process will most likely run into memory issues. &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-18-04"&gt;Checkout this tutorial for setting up a Swap&lt;/a&gt;. A 1G Swap should be more than enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Ready the app for deployment
&lt;/h2&gt;

&lt;p&gt;Back in your local app add a new file called &lt;code&gt;.buildpacks&lt;/code&gt; with the following contents&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/heroku/heroku-buildpack-nodejs
https://github.com/luckyframework/heroku-buildpack-crystal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Step - 3 Deploy
&lt;/h2&gt;

&lt;p&gt;Make a new commit. Finally you're ready to deploy. Run the following commands locally. The first will add your server as a remote origin for your git repo. The second will push the code to the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote add dokku dokku@ip:app.example.com
git push dokku master 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 - Set up SSL
&lt;/h2&gt;

&lt;p&gt;Dokku lets you easily set up a Lets Encrypt SSL certificate. Run the following commands to do so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set --no-restart app.example.com DOKKU_LETSENCRYPT_EMAIL=your.email@example.com
dokku letsencrypt app.example.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's all there is to it! Each time you push to your Dokku server your app will update and migrations will be ran automatically.&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>dokku</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From GhostCMS to Hugo - Snipline's New Blog</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Mon, 06 Apr 2020 10:22:44 +0000</pubDate>
      <link>https://dev.to/mitchartemis/from-ghostcms-to-hugo-snipline-s-new-blog-bmo</link>
      <guid>https://dev.to/mitchartemis/from-ghostcms-to-hugo-snipline-s-new-blog-bmo</guid>
      <description>&lt;p&gt;I recently switched &lt;a href="https://snipline.io"&gt;Snipline&lt;/a&gt;'s blog from GhostCMS to Hugo. I thought I'd share why I did it, the benefits of GhostCMS and Hugo, and the alternatives that I explored.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.snipline.io"&gt;You can see the new blog here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The blog looks mostly the same as it did before—using the delightful Casper theme, so why move systems?&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of GhostCMS
&lt;/h2&gt;

&lt;p&gt;GhostCMS is quite simply a fantastic blogging platform. I love the interface and it doesn't try to be something more than that (Looking at you, WordPress). The documentation is good, there are plenty of themes, and most importantly it's open source and self-hostable for free. This is ideal for Snipline, which is a small business that needs to save on costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Switch to Hugo from GhostCMS?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Faster
&lt;/h3&gt;

&lt;p&gt;I'll be honest and say that speed is the least of my concerns. The blog was already fairly fast, but it's nice to see this minor speed increase.&lt;/p&gt;

&lt;p&gt;Here's the Google Lighthouse statistics for the Ghost version of the blog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vaHQlcmp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yg12pf2e4eu9i2wgbi96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vaHQlcmp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yg12pf2e4eu9i2wgbi96.png" alt="GhostCMS Lighthouse stats"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the same stats for the Hugo homepage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Daaoe6My--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kggy5bbc2g3a2k6e86ew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Daaoe6My--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kggy5bbc2g3a2k6e86ew.png" alt="Hugo Lighthouse stats"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Less Maintenance and Responsibility
&lt;/h3&gt;

&lt;p&gt;Using self-hosted GhostCMS means keeping Ghost up to date and dealing with any server issues that crop up.&lt;/p&gt;

&lt;p&gt;With Hugo I'm able to use Netlify for free (or any other static hosting service) and most importantly I don't have to worry about uptime or keeping dependencies up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternatives that I explored
&lt;/h2&gt;

&lt;p&gt;I've tried far too many static site builders recently. Here's the list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ef4/prember"&gt;Preember&lt;/a&gt; (Ember JS plugin).&lt;/li&gt;
&lt;li&gt;Custom build my own static site generator in Crystal.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuxtjs.org/"&gt;NuxtJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gridsome.org/"&gt;Gridsome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://middlemanapp.com/"&gt;Middleman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/JohnSundell/Publish"&gt;Publish&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This naturally begs the question...&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Hugo over &amp;lt;insert-static-site-generator&amp;gt;?
&lt;/h3&gt;

&lt;p&gt;Hugo was my not first choice—I actually circled back to it. At first I found it confusing and overwhelming to work with.&lt;/p&gt;

&lt;p&gt;But I've since started reading Hugo in Action which has helped my understanding greatly, and now I'm beginning to become more productive with it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Preember
&lt;/h4&gt;

&lt;p&gt;I'm a huge fan of EmberJS so I tried building a prototype with Preember. Although generating blog posts was easy enough I had to work against the system to get feeds and pagination in place. That along with the lack of community compared to other systems meant that I didn't think it was worth persuing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Nuxt.JS
&lt;/h4&gt;

&lt;p&gt;I have had success with NuxtJS on other sites. VueJS (What NuxtJS uses) is a pleasure to work with. I think if I had not chosen Hugo I would have chosen NuxtJS. My only issue with it is that to export to SSG requires extra steps (Telling NuxtJS where to fetch the data and generate dynamic routes). I also do not need the additional benefits and weight of Server Side Rendering and Single Page Applications which Nuxt provides. In Nuxt's defence, I'm not using Hugo as Headless, so using local markdown files may be easier, but I did not explore this option at the time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Gridsome
&lt;/h4&gt;

&lt;p&gt;Gridsome I tried and initially liked, but it seems to rely heavily on GraphQL—which isn't necessarily a bad thing—I just don't know the technology and so it's another thing to learn. Also, I had this issue where my blog posts would not order by publish date and I couldn't for the life of my figure out &lt;em&gt;why&lt;/em&gt;. I gave up out of frustration.&lt;/p&gt;

&lt;h4&gt;
  
  
  Middleman / Jekyll
&lt;/h4&gt;

&lt;p&gt;I have an extensive past with Middleman, and although I like it, I would rather not continue using it due to dealing with Bundler/nokogiri. Same goes for Jekyll, except I have way less experience with Jekyll!&lt;/p&gt;

&lt;h4&gt;
  
  
  Publish
&lt;/h4&gt;

&lt;p&gt;John Sundell's Publish looks really promising, and I did get a mostly working prototype working with this. I kind of love the &lt;a href="https://github.com/johnsundell/plot"&gt;Plot&lt;/a&gt; HTML system which it uses. Apart from that, when I was using it, I had to dig into the internals of the code a lot to figure out how to do certain things. This was interesting but time consuming. I think I will keep my eye on this for the future and see how the project progresses.&lt;/p&gt;

&lt;h4&gt;
  
  
  EleventyJS
&lt;/h4&gt;

&lt;p&gt;Another stack that's recently popped up on my radar is Eleventy.js. This looks promising but I found it a little too late!&lt;/p&gt;

&lt;h3&gt;
  
  
  Thanks for Reading
&lt;/h3&gt;

&lt;p&gt;So there you have it! Overall I'm satisfied with my decision to use Hugo for this blog and I look forward to using it on future projects.&lt;/p&gt;

&lt;p&gt;Perhaps I'll move the main Snipline website over to it sometime in the near future!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>hugo</category>
      <category>staticsite</category>
    </item>
    <item>
      <title>Replicating the MacOS Search TextField in SwiftUI</title>
      <dc:creator>Mitch Stanley</dc:creator>
      <pubDate>Sun, 22 Mar 2020 15:32:11 +0000</pubDate>
      <link>https://dev.to/mitchartemis/replicating-the-macos-search-textfield-in-swiftui-2hp7</link>
      <guid>https://dev.to/mitchartemis/replicating-the-macos-search-textfield-in-swiftui-2hp7</guid>
      <description>&lt;p&gt;I wanted to add a search text field to a MacOS app that I'm working on and soon discovered it's not available in SwiftUI (as off 5.1). I've put together a quick replication which mostly works, but unfortunately involves some hackiness.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gdjgCurj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uje3uwqxxogogpex9d5e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gdjgCurj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uje3uwqxxogogpex9d5e.gif" alt="Preview of Search TextFIeld"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This extension removes the focus ring entirely.&lt;/span&gt;
&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;NSTextField&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;open&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;focusRingType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSFocusRingType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;none&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SearchTextField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Binding&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isFocused&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Search..."&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ZStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isFocused&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;lineWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isFocused&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"magnifyingglass"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aspectRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;contentMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kt"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;onEditingChanged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;editingChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;editingChanged&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFocused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isFocused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFieldStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PlainTextFieldStyle&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
                    &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"xmark.circle.fill"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aspectRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;contentMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PlainButtonStyle&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here's how you use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;SearchTextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;searchQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Search..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;idealWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;minHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;24.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;idealHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;21.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;21.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;There are a few issues with this solution. Hopefully in a future version of SwiftUI this will be fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing the Focus Ring from All TextFields
&lt;/h3&gt;

&lt;p&gt;This code creates a pretend TextField which holds the magnifying glass, a real TextField, and the close icon inside of it. If you remove the NSTextField extension you'll see the real textfield when focused. It's currently not possible to disable a specific focus ring so you'll have to reimplement them if you need to show them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fake Focus Ring Doesn't Update on Focus
&lt;/h3&gt;

&lt;p&gt;You'll notice in the GIF above that the focus ring does not show until you start typing. Not ideal but usable. If anyone has a suggestion for fixing this I'd be happy to know!&lt;/p&gt;

&lt;h3&gt;
  
  
  No SF Symbols in MacOS
&lt;/h3&gt;

&lt;p&gt;Surprisingly, MacOS does not have native support for SF Symbols! I used &lt;a href="https://github.com/knezzy/sfsymbols"&gt;this tool&lt;/a&gt; to convert them to PNG and import them into my assets catalog. The symbols used are &lt;code&gt;xmark.circle.fill&lt;/code&gt; and &lt;code&gt;magnifyingglass&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>macos</category>
    </item>
  </channel>
</rss>
