Getting started with Rust 🦀 2021: 7a. Building a GUI app in Rust [Part A]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so with the changes so far in the library i think we're in a pretty good shape in using the news api create to build applications on top of and i thought why not use it to build a native gui app in rust so i built one and here is how the app looks like so the app is named headlines you can scroll through the list of articles it is also responsive and comes with built-in support for dark mode you have the three buttons here you can change the theme you can refresh the list of articles and the articles are fetched from the news api create we built before and there is the close button that quits the app so this episode is a journey into the whole process that went into building headlines right from the wireframing of the app to planning the widgets we need where and how we want to render the articles and the implementation in code if you feel that you need more context you can watch the last three episodes the links are in the description [Music] [Music] so i start by collecting the data points we need to render the app at minimum we need the title of the article the url and a description which is a field i later added to the news api crate once i had the data model i went ahead and created a rough wireframe of the app just to lay down my thoughts on paper and get an idea on where and how i want the articles to render within the app so at the very top you have the title bar to the left you have the name of the app or the logo and to the right you have three buttons the first one will let you change the theme the second one you can use to refresh the list of articles and there is the close button which quits the app then we have the app header which simply contains the top headlines label next we'll have a scrollable container widget which will contain a news card widget and this is again a container which and it is the newscard widget which will contain the title of the article then the description and a read more button which points to the source of the news so once i had a rudimentary wireframe in place it was time to choose an appropriate gui library so i evaluated a few popular gui libraries in rust there's one called iced which is a reactive library based on the elms model view update architecture i'll leave a link if you want to know more on that then we have druid which by the looks of it feels like it builds a reactive layer on top of a retain mode api as it uses gtk underneath on linux then we have egoi which is an immediate mode library inspired from the c plus plus image ui framework we also have a recent one called 60fps which is also a retain mode library but it also gives you a declarative language to define your ui which works similar to qt frameworks qml language so usually graphic libraries will fall into two categories depending on what api they expose to handle the rendering of the scene or layout the first one is retain mode api and the other one is an immediate mode api the major difference between the two is that the retain mode graphics builds and buffers an internal model of the widget layout and only renders changes as needed whereas in immediate mode graphics apis there's no such internal model of the scene being maintained and the scene is rendered every frame and that the client aka the library user controls the rendering of the scene so i ended up choosing egre because i found the api to be very fluid and easy to use in fact i was able to get the first version of headlines up and running in a day which could have been even less if he factored around styling and an api config bug that i ran into so big shout out to emil for making such an intuitive and easy to use library apart from the gui library we'll also use a few additional crates so there is config which will help us persist the api key and any state that we want to persist across restarts then there is tracing which we'll use for logging information on the app then there is surday which will help us serialize data structures for config and the news api create which we built before and that will help us make an http request to fetch the news articles so with the library selection and wireframing done i created a brand new cargo project and this time i'm using the workspace feature of cargo which lets you manage multiple local crates in a directory so in this manifest file we have defined a workspace section and in the members field we have defined the local crates we want hardware to manage so there's our news api create from the previous episode and the headlines crate which we have created as a binary crate in the cargo.tunnel of headlines crate i've added e-frame as a dependency which is a crate equi exposes as a top-level library to make building applications easier it simply re-exports type definitions from sibling crates such as epi egre and friends so within main.rs let's remove the hello world print line now before we write code let's briefly go over an overview of gui libraries so the general interface exposed by graphic libraries is such that you first have to create a top level window object with the window object created one can then add widgets into that the widgets usually are of two kinds container widgets and ui widgets container widgets as the name suggests are widgets that that can contain other widgets and they are usually used for aligning and orienting the child widgets contained within this and ui widgets are basically widgets that contains some state and have a visual representation on screen so this would be like a text label or a button and then you also have an event handling loop which handles inputs and interactions by the user the event loop might also update the internal state of the widgets finally there is a render api which renders the state and layout of widgets by calling the underlying graphics api such as opengl now this was a very abridged overview but you get the idea so let's start writing some code as mentioned before we need to create a top-level window object and e-frame provides a function called run native that creates a window and this function takes in two params the first one is an app instance and this is going to be a box of dyn app an app is a trade from the epi crate and the app trade has a bunch of methods that gets called at different states during the life cycle of the app so we're gonna have to create a type definition and have to implement the app trade on it so i'm gonna define a struct called headlines let's keep it a unit struct as of now and i'm going to implement the app trade for headlines and if i save this i'm going to get an error and it says that not all trade items have been implemented so i'm going to use rust analyzer to quickly fill in the method for us so as you can see at minimum we need to implement the update method and the name method the name method is simply going to be the name of our app and the update method is where all the magic happens so within the update method we can add widgets and also react to any state updates that happen within the app and to start things with we need to define a container widget so egree provides us with a few container widgets and to start things with we're going to use the central panel widget and if you take a look at the api is that widgets are usually initialized with either a default or a new method and then you have the show method which actually renders the app to the screen and the show method gets past two parameters the first is a context object that we will pass from the update method and the second is a closure which takes in a ui object as a parameter and using the ui object we can then add child widgets within the container widget and as we go further you will notice that it's a repeating pattern in the equi library that almost all of the container widgets have this sort of api so let's go back to our editor let's define the central panel widget i'm going to initialize it with default and then i'm going to call show and we already have the context object and for this we'll create a closure and closures and rust are created using double vertical bars between the bars you can add parameters this closure only takes in a single parameter which is a ui instance ui is a handle for us to create widgets within another widget so within the body let's add our first basic widget to start with we'll use label widget and this takes in a string that will get tone on the app screen so let's type a random text here for now and then to fix the two errors we have let's create a headlines instance i'm going to call this app and i'm going to initialize it by just using the name of the struct since it is a unit struct and this still won't work because we need to box it and for the native options let's create a window option object and let's use the defaults and change this to window option and we should be good to go i'm going to do cargo run and there you go we have our first widget on the app screen so let's continue with that now instead of rendering a dummy label widget let's try to print a list of news articles we'll use dummy data for now since we aren't hitting the news api endpoint so i'm going to initialize headlines with an articles field and this is going to be a vector of something called newscard data and newscar data is going to be another struct where we have the title field as a string then the description and the url with this change we'll get an error because headlines now needs the article field to be initialized so instead of initializing it the manual way let's define a method on headlines so i'm going to create a new method and this will return us a new instance of headlines and for the articles field let's initialize it with a dummy data and we're going to create a dummy data from an iterator here so let's define our iterator and we'll use a range iterator for that let's define 20 news articles and map the elements of the iterator to a news card data instance so for the title we're going to put a dummy title here same with the description and for the url as well so what we have done here is we created a ranged iterator and to do that we have to specify the lower bound and the upper bound of the iterator and this returns us a lazily evaluated iterator which we can then call the map method and what map will do is it will run this closure for each element of the iterator and convert that into a newscard instance the argument that you see here is going to be the elements of the iterator so for every call a is going to be initialized with 0 1 and so on so we have the iterator initialized here and since iterators in rust are lazily evaluated in order to evaluate them and produce the results out of it we're going to pass it to the prometer method of vector which will consume the iterator and create a vector of new scar data from it so there is our new function defined let's use that to initialize the headlines instance here and they should compile so then let's go back to the central panel widget and let's use the dummy list of articles to render the articles to the screen and we can use the label again and we can render the title the url then the description and we have a reference error here because the for loop is trying to own the article fields so in order to fix that we can use the ampersand which will only give us a read access within the for loop with that we should be good to go so let's run this again and there you go we have a list of dummy news articles being printed but if you take a closer look we're only rendering about 10 articles in the screen and the rest of them are nowhere to be seen they're still there if we try to expand the window we can see the remaining of them but this is not the experience we want instead we should be able to scroll the window and be able to access other articles as well so in order to fix this we're going to use a scrollable widget within central panel so i'm going to create a scroll area widget initialize it with auto sized function and there is again a similar show method on that where we'll pass the ui from the central panel widget and as the closure we pass ui again and within scroll area we will place the list of news articles so with that change we should be able to render articles so as you can see we now have scrollable list of news articles which is an improvement so let's make more changes let's expand the text size of the article and also set a predefined window size so that it looks close to the wireframe we designed before so to set the window size i'm going to modify the window options object and set the initial window size and this is going to be an option type so i'm going to wrap it with sum and then create a vector that contains the x and y points as the number of pixels so i'm going to define a qhd resolution of 540 by 960 and this is going to be floats so i'm going to suffix it with dots and there is an error about mutability so let's add mute and this should work and for configuring the font size of the article we'll use the setup method from the app trade and rust analyzer has auto-generated the method for us so i'm going to reformat this so that we can read it we can ignore these as of now we'll come to that later so setup is another lifecycle method from the app trade and this is a one-time call that happens during the initialization of an e-app so as you can tell we can use it to do any preloading or configuration of the app so we're going to use it to configure the font so let's define a configure font method on headlines this isn't defined yet so i'm going to generate the method on headlines using rust analyzer and this won't have any return type so i'm going to remove this let's remove the to-do as well and within configure font we'll need to do three things first we'll create a font definition object then we'll load up the font then set the size of different text styles and finally load the font using the context object so this method will also receive a context object so let's pass the context object let's remove the underscore because rust treats underscore variables as something you can ignore let's fill in the type of the context object and this is a reference to the context object so we're going to prefix this with an ampersand so first let's create the font definition we'll initialize with defaults and the font definition instance has a font data member which is simply a b3 map and using that we can insert our own custom font that we want so if you notice in the directory structure i already have a font copied in the local directory so i'm going to use that i'm going to rename this font eat to meslow lgs that's the font name and i'm going to use a type called cow which is a copy on write data type and this is required because of how the font data field has a requirement on the value of the map to be of of cow type within the borrowed variant of cow i'm gonna include the data from the font file and this resides in in the parent directory so let's load this up and this should work okay so this needs a strings so let's convert that to a string and this needs an import so let's import that and we don't have on def mutable so let's make it mutable and this step is also done so let's remove that so with the custom font defined let's try to set different font sizes for different text styles so we can use the family and size field which is again a b3 map to insert the desired sizes for the built-in text style the first one is going to be for the heading and this will be for the proportional font family of 35 points this is going to be a tuple so let's wrap this in a tuple and let's also set the textile for the body to be of size 20 and then since egree comes with built-in font styles we're going to have to tell equi to give priority to our custom font and in order to do that we'll need to insert our custom font at the front so let's do that so i'm going to use the font def instance again and in the fonts for family field we're going to get mutable access to it and for the proportional font family we're going to insert our font to the front and finally using the context object we can set the font by using the setfont's method and pass in the font def object so with that change we should see quite a bit of difference in how the app looks like and hey we now have bigger font sizes so let's go ahead and add a bit more customizations so i'm going to speed things up since we're just going to be adding a bit of style changes to the app and that's probably going to take more time if i go through it step by step so i'll see you on the other side [Music] okay so let's take a look at the changes so far we refactored the loop which renders the articles into a render newscards method and this is where the bulk of our style changes happen so for each article we're adding a bit of room for our widgets to render so we have added a padding of five and after that we are rendering the title of the article with a symbol and then we use the colored label function to render the title invite we do the same thing for the description this time we create a new label object because we want to do a bit more customization such as getting the text style either you can use the label helper function or you can create a new label instance using the new method and can then add the widget using the add method then we come to hyperlinks where before rendering the hyperlink we change the visuals of the hyperlink color to be the sign color and then add a bit of padding and then within a new layout area that aligns items from right to left we then add our hyperlink within that and then there is a padding at the end and a little horizontal separator so we can distinguish each news card from the other so with those changes our app looks something like this which is already looking pretty good so far next we'll try to add the header the top header for the app and also a footer which will contain a few metadata or headlines but before we do that let's move the headlines definition and the input block into its own modules so we can keep the main rs file clean so i'm going to create a new module called headlines and then we will move the struct and the input block in headlines to help rust recognize the headlines module i'm going to declare it with mod headlines and let's also move the news card data and we have a few errors so i'm gonna quickly fix these [Music] so the errors were mostly about import issues and not having the pub keyword for the exposed modules so now that we have headlines extracted into its own module let's go back to main and let's create two helper functions the first one is going to be render footer and this is going to take a mutable reference to the ui and let's also create the render header function and it takes the same ui let's import that so for the header we are going to first add the top level headlines label and for this we're going to use a container widget called vertical centered and this takes in a closure and we will add a heading widget and with the contents let's put headlines then we'll add a bit of space and use the same padding amount and then let's also add a separator with custom spacing we'll use 20 points as the distance and we'll add it using the add method so with that finished we're going to call this right before the scroll container widget and let's try to run and see how it looks like so there we have our header and let's now add the footer widget so for the render footer function we'll use the top bottom panel container widget and we'll use the bottom variant we'll give it an id of footer and then call the show method and this takes in a context object so let's pass the context object and the second param is again the closure which gets the ui handle so then we'll use the ui handle to render a vertically centered widget and this again takes in a closure and within that we'll first add a space of 10 points and for showing a bit of context or metadata about the headlines app we're going to add three things first we'll add the api source website info then we'll add a link to the egoi framework then we'll put the github link to the headlines source code so for the first one let's add a label let's use the monospace text style for the egg framework link let's add a hyperlink and for the text of the url we'll put made with egui and let's also use the monospace textile for the github repository link we'll do the same thing and then let's add a bit of space at the bottom so there is our render photo widget so let's call this function right below the scroll container widget and we don't need this ui to be passed from the parent container since we're going to have access to the ui handle here so i'm going to remove that and let's try to run this so there you go we now have the footer being rendered as well so next the final piece of the ui that we have to add is the top panel of the app which contains the control buttons so let's add that so to render the top panel with the controls we're going to render it right above the central panel widget so i'm going to call self dot render top panel and this is going to be a method on the headlines struct so i'm going to generate the method here and let's remove the to-do here so within this method we'll first define a top bottom panel widget and inside that we'll add a menu bar then we'll use two layout widgets to render the logo on the left and the control buttons on the right and there will be padding before and after the panel so let's start with the top and bottom panel and we'll use the top variant this time let's call it top panel for the id and let's call the show method this gets the context object again so we will pass the context object and within the closure we'll then use the equi menu bar widget this takes in the ui handle and again a closure and within the closure we will then add a layout and this layout is going to be from left to right and for the contents of the layout we'll add the logo of the app as a label let's use this and let's keep the text style to be of heading so this is going to be our logo and then below that we'll create a new layout for the controls we'll do ui with layout create a layout and this time it is going to be from right to left and within the closure let's first add a close button so i'm going to do ui dot add button dot new and let's use the cross symbol we're going to use the text style of body and you'll notice that we're capturing the result of adding the widget to the ui as the close button variable and this is because we'll use it later to add event handlers to this button so so that if you click the close button the app should then close so next let's define a refresh button we'll do the same thing and for the third button let's add a theme button and let's fix those errors so with the control buttons and the logo added we're done with the top panel widget let's go ahead and take it for a spin we'll pass in the context object here and let's run this again and hey we now have our control buttons and the logo on the left now it feels a bit crammed so let's add a bit of space at the top and the bottom so let's go back to headlines and right before the menu bar we'll add a bit of space and let's use 10 points and also at the bottom and let's run this again so there you go we have a little bit of breathing room for the widgets so with the changes so far we are pretty much done with the static front end ui for headlines and i'm gonna call it a day as the video is probably going too long so in part b of this episode we'll be making headlines dynamic and make changes to fetch real data from the news api crate and add a few more non-functional but nice to have changes so in the process we'll learn how to use rust threads channels within the context of an immediate mode api which has its own challenges so thank you for staying with me this far and i'll see you in the next one
Info
Channel: creativcoder
Views: 10,405
Rating: undefined out of 5
Keywords:
Id: NtUkr_z7l84
Channel Id: undefined
Length: 27min 38sec (1658 seconds)
Published: Sun Oct 10 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.