UI hacking: Improvements to file picker keyboard navigation

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
well hello friends welcome back to the program today we are going to work on keyboard focus in serenity uh and i have a particular ui thing that i want to improve and let me show it to you so it's the open file dialog this standard system dialog that every app can use and it's pretty okay to navigate it with the mouse but if you use the keyboard i noticed this super irritating thing if i type something here so now i have the file name text box focused and if i want to tab around to other widgets as you can see i'm moving back and forth here it takes a silly number of taps to get one lap around all the widgets so that's uh from from here and back again that's uh one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen sixteen um taps of the tab key to cycle through all the widgets and that is just doesn't feel good there are too many focusable keyboard focusable things in this dialog and also uh something else that's kind of weird i notice here is that when you tab away from a text box then we still show the selection here uh just grayed out and you can see it up here as well and i'm not sure that's actually useful um for anything because what if you have a selection like that you tab away and then you click on the thing if you click anywhere then you you set a new selection right and if you tab back to it it automatically selects everything so that doesn't seem particularly useful like you tab away you tab back everything is selected um you have a selection you tap anywhere and it clears the selection yeah so we're showing this um we're showing the previous selection and text boxes when you move away from them but there's no way to recover that selection by clicking in the widget anyway so it's kind of that's kind of silly so let's let's do something about that as well um but yeah let's let's start by looking at the focus thing so we have this mechanism where you can um you can see focus rectangles by turning on an environment variable so here we can see the the cyan rectangle around the text edit and if we open up for example the search and replace bar you can see focus move around and this cyan rectangle just helps us see focus better so if we open up then there is no focus rectangle because the open dialog runs a separate process of course so we have to actually that's a system service so we have to spawn that system service with the environment flag well we can do that we just do this gui focus debug equals one okay let's boot up okay here we are so um now let's let's look at that issue right so when i tab away um [Music] we're tabbing individually through each of the items here and then we go here and then we tab individually through all of these toolbar buttons up here um and then comes this guy the big view and then we're finally back at the file name text box so this feels very excessive in terms of keyboard focusable widgets really we should make the essential widgets keyboard focusable and stuff like um i think these toolbar buttons here don't need to be keyboard focusable at all there are keyboard shortcuts for them if you wanted to use them from the keyboard but i don't i don't think it adds much to have them be keyboard focusable um you could we could have a mode where i'm just thinking like if if you are completely unable to use a mouse it is nice to expose these buttons to the keyboard and to have a way to discover the shortcuts so perhaps we could have something that exposes shortcuts for every everything that has a shortcut just a display as a shortcut if you press some special key combo or something like that but um just as a first cut here we can remove six tab steps by just not having toolbar buttons focusable um and i think actually that would make sense here as well because you can't you can't tab back from the text input because it swallows tabs but what if you go here and then you tab yeah then you end up up here um actually you can't see that so well so let me just um yeah so by by going down here i can i'm able to tap my way up to the toolbar and these are actually keyboard focusable as well which i i think that's that's not a really useful feature um if a user wanted to use these actions from the keyboard i think they would be much more likely to use the menu anyway um the menu is already streamlined for keyboard access with um alt f and then n with the underscore and everything um or underline so everything is kind of already set up for fast keyboard access um so yeah so let's make these toolbar buttons [Music] toolbar buttons should be non-focusable toolbar button has a current focus policy of tab focus so tab focus means that you can a widget can be focused by tabbing to it it does there's tab focus click focus and no focus and then you can also combine click and tab focus but basically um the focus policy we want here is just no focus so that it can simply not be focused i think i think that actually makes sense so let's see what that does to this dialog so now we tab tab tab we get up here and then from here we jump down there so i think i think that's already a huge improvement that you don't have to cycle through these things here that's six steps removed strong improvement and then all of these guys here i don't know that that you should tab all the way through them i feel like i feel like this should be considered one big widget and then it has selection so it's a bit like a what is it like a radio button group or something like that um where this tray area here would have focus and then it has a selected item in it uh that's gonna be a bit harder to solve i think because these are actually buttons individual buttons [Music] so we'll have to do some some hacking to to achieve that but yeah so anyway let's see let's start with that toolbar thing so toolbar libgui make toolbar buttons non-focusable by default toolbar buttons are meant as a com quick meant for quick mouse access to common actions um not really as um while keyboard users um cuboid keyboard uh well fast keyboard or quick keyboard access is normally achieved the keyboard shortcuts uh and underlined menu items this makes interfaces with many toolbar buttons eg the gui file picker uh a lot nicer to use via keyboard a lot nicer to navigate okay yeah that's that's an improvement so we're we had 16 steps and we took six out um and how many steps are those that's one two three four five steps so if we can take those steps out as well and turn it into a single step i feel like that would be great um but before we do that though let's do something about the selection thing so the solution here is probably to just not um wait text editor paint paint event the solution is probably is not a paint selection in non-focused text editors so text editor paint is pretty big and where do we draw the selection um my goodness it's been a while since i looked at this stuff trailing white space if physical line has a selection then oh yeah yeah right so this is how we render um text selection is that we we render all the text initially uh with no selection visualized uh and then on top of that we will draw a rectangle in the selection background color and then we draw the selected text on top of that in a contrasting color to the selection background um right here we pick colors so background color if focused is the palette selection and if it's non-focused then it's the inactive selection but right so so but we always paint the text so i think maybe what we want to do is just um just this right and then wait where was that check if we have if the physical line has selection and is focused yeah because if we're not focused then we can just not bother in fact then we don't even need these temporaries we can just slot them in where they would go like that okay let's see how that looks okay so you have a selection there you go down here and you don't see the selection up here anymore you click there behaves as before and you don't realize that you lost your selection click click click click yeah that feels pretty reasonable so i think let's let's do that only paint text editor selection when focused um showing the selection in non-focused text editors was not really useful since refocusing the widget would snap or would clear are clear or otherwise change clear clear we clear or change the selection immediately anyway yeah so yeah that's that's nice this is one of those things that that has always bothered me a little bit but i never articulated what i didn't like about it i guess the thing i didn't like about it was that it was useless because it showed you a selection that you couldn't recover anyway so it's just a ghost of a previous state that you can't return to um which we obviously we shouldn't be showing that type of thing in the ui so now the remaining thing is the tray on the left side with the common locations so common locations that's in the file picker dialog it's a common locations frame that has a vertical layout um yes but okay so here is where we iterate through all the common locations and then we add a button for each one sure that's fine so i guess hmm [Music] what is the best solution for this either if you have some sort of mechanism where the the frame has the focus um but it still allows you to tab through its children or no not tab through them wait how should that work even like what would be the intuitive way for this to work i i think what seems intuitive to me is that you would um wait this is the wrong app um [Music] you would tab and you would focus this entire thing and then you would keyboard navigate up and down so that already works because uh up and down is a sort of like tab um so you would keyboard navigate up and down but but they wouldn't tab focus hmm so there are two approaches to this i guess either we make a new tray widget that sort of implements this thing here and we stop using a composite widget because right now it's a composite widget built out of a frame containing vertically laid out buttons right uh we could turn this into a tray widget that just paints and manages mouse events and everything that does feel like a bit of a hassle though like compositing the buttons like this feels nice because you don't have to don't think about all the other stuff but i guess the up and down thing how would i want that to work though i would sort of i guess if we turn off the because this is kind of not visible with the focus debugging wrecked on i think i would like it if they appeared as if you were hovering over them like this like if they would look like this when you cycle through them with the keyboard that would be really sweet uh except for the currently currently already open one of course because now if i put the focus here and then i keyboard up and down actually i can i can't barely make out the focusrec there is a focusrect but it's like it's not a good color so you can you can't really see it let's get the uh let's get the magnifier here so i can show you oops so let's amplify the magnification and take a look at that um wait hold on let me create some space um you see that it does have a dotted focusrect around it [Music] oh no look at that it bleeds over the edge of that border and it's not not super nicely aligned either although it is well aligned in the unpressed case hmm oh i see so it's only the depressed one that has bogus alignment well let's not worry too much about that although the fact that it bleeds over the right hand side that does bother me um but we should probably not draw a focusrect around those things anyway because focus should be at the tray widget level um yeah i don't know that i don't know that i see a way out of this in a nice way that doesn't involve creating a new trade widget um but that is probably pretty painless ah what the heck i didn't write any new widgets for a while so let's just make a trade widget um so that would be let's call it tray tray i like that name for those tray.h all right copyright gui widget okay and let's come up with this thing so class tray um let's inherit from gui frame actually seems like a good choice yeah this is gonna be neato okay constructor and then what we want to do in this thing i guess we want to paint and then we want to add buttons to it um [Music] yes so let's see virtual boy paint event um yeah let's uh let's just start on it we'll start by painting it and then once we can paint everything then we'll figure the rest out so okay okay it's it's nice to have to compose widgets out of other widgets but um the moment you want to have sort of custom behaviors um it gets harder to compose um maybe maybe there's a logical extension way to to do compositing here instead but nothing sticks out to me so let's just power through this uh we'll just set up a paint event and we'll need a painter and then we'll also want to add some bitmaps and we'll want to have items so add item and what is an item an item has a string and an icon i guess um let's let's say that you have a string in an item yeah string in a bitmap icon okay and then um we could give you we could have a callback although i like it better if um what you get is something like on item activation item activation yeah so if you click on an item then we will fire this callback okay uh and maybe we allow you to pass some custom data so text icon and custom data and the custom data will be passed to you here let's say okay and then we want to store these in something so like a vector of item where item is a struct that has a text non-null ref footer to graphics bitmap and the custom data so literally the things that you had here okay tray add item poof and then we just do m items append or no append forgot to move move and move there we go all right and then after you add an item we should probably repaint the widget so we'll just ask for an update okay and then let's instantiate one of these things so that common locations frame is instead going to be a gui tray be common locations tray um and uh yeah we can just set it up like that common locations tray and then common locations frame now becomes a common locations tray bloop you will be a goofy tray all right this the background role we can set up automatically in the tray so we will do that in the constructor look look at that uh that just makes sense why doesn't it highlight anyway let's see so common locations tray why is that initialized all the way up here feels like we could use it we could initialize it down here where we use it yeah okay so instead of add button like this um this now becomes a bit more like common locations tray add item so the item will be [Music] whatever the text was location.name all right and then the icon so that's this guy right here and the custom data will be the path so location.path okay then we don't need that temporary uh and all of this style information all right so we have on click here so this becomes we don't we're not gonna have a button instead we're just gonna have the tray um on item activation which will then simply uh it will receive the custom data right so string custom data uh set path path um yes i think that's pretty good maybe we should allow icon less tray entries actually that's probably okay so we'll make this a ref better instead of a non-null rafter if you want to add something that's only a piece of text that should be okay all right yeah and then the rest of this is just cool info hmm common location buttons what is that used for so we keep track of all of these buttons that we instantiated because if you switch to oh right so if you switch to a directory that's in the common locations tray we update that button to be checked um right so that's something we have to do a bit differently um i guess we can we need a way to toggle um buttons here so i'm thinking should this return an index maybe that seems kind of sensible it will return the index and then we would just keep track of the index set checked size t index um set item checked that seems reasonable so how would we use that and the file picker um common location button it wouldn't have buttons but it would instead have an id tray item index right um index and index boom okay yeah i'm just going to bring this with me here as a comment um so i can reference what it's supposed to look like okay and then what am i missing model on complete what is files oncomplete the model on complete is that like on complete that's like that's not a well named thing we should find a better name for that uh but basically like when when something when you change the path of the file system model and then it finishes successfully i guess then that callback gets invoked so let's see wait these are copies well um so anyway what we want to do here is to just get that tray oh that's why that was up there i see common locations tray um set item checked um oh shoot and then the index okay so [Music] location button dot path um right so that needs a boolean for whether it's checked or unchecked cool and same concept here so location button dot tray item index [Music] we need to get the tray okay lots of stuff um so these guys apparently need a checked flag um which we can then update so uh what we'll do is have oh this thing is supposed to return the index actually that's pretty easy we just return m items or new indexes and item size there we go set item checked uh and then m items add index checked is equal to checked then uh that's a no op otherwise i'm items at index.checked is checked and then repaint okay i'm thinking if you check one of these boys we should also uncheck all the other boys um i feel like so if checked yeah so if i is not index then m items index checked is false and actually in fact we can do this in a loop like this instead so um oh wait no let's not get fancy let's let's just be simple let's just be simple we can do fancier things later because it looks a little bit messy but that's okay okay so how do we paint this so i guess we decide that these things have a certain height previously we used 22 as a fixed height as you can see here so 22 seems reasonable so we just have to lay these out into rectangles so we'll want to have a style painter so that we can paint buttons and then let's see so maybe we can simplify this by letting these things know their index uh and they can also have a way to get their wrecked let's do that that's pretty neat because it allows you to be a bit more expressive like for example we can do this if item dot index is not index yeah it's a bit nice and sure so how do you compute your rectangle well it is a function of your index so x let's just say 0 y is your um index times 22 for the height let's say just uh magic numbering it for now the width is going to be oh we need the we need a reference to the tray here actually um that's okay though we'll we'll do that uh trey const sure uh so that's the tray frame interact with tray frame in a wreck no no wait the height is 22. const expert item might 22. something like that okay and then for auto item in items item wrecked for this sure and then we simply paint that thing so gooey painter painter painting to this widget uh clip to the event rect so that we don't overdraw unnecessarily and then um i guess we'll just start we'll just start by drawing some rectangles so draw erect uh direct color um black whatever and then we'll worry about all the other stuff later okay let's see if we can get this thing even painting something said item check oh shoot i forgot to actually add this to the build okay trey is not a member of color roll wait why hmm that doesn't seem right palette yeah it is a member i have to give that a type that's very silly um this cannot be narrowed sure uh this thing wants a size t return type and everything is good okay wow taking your sweet time there crash very crash okay what did we do wrong file picker constructor at line 46 we are screwing up sure what did we screw up 46 um 246 sorry on item activation uh i'm assigning to that but oh wait wait i forgot to if i want to use it from gml like this i have to actually expose it so what you got to do for that is you have to register widget [Music] and that we need to do here so register widget in the gui namespace and the widget name is tray otherwise that's not going to work okay so our tray is here it's looking a little bit crap but it's a start so let's start with um i guess well let's start with uh fill the background color true and then let's make sure we paint a frame around ourselves so that would be just calling up to gui frame paint event before the regular paint um and then we want to make our offsets be frame aware so instead of zero here we want to use the frame thickness as the as the x value wait why does not work non-static remember oh right yeah of course trade at frame thickness um and same thing here frame thickness plus you just gotta take that frame there's a two pixel frame around this thing well there's going to be so we just have to take that into account here we go it's starting to look a little bit better um next step would be i guess to paint these as regular buttons so instead of just doing a draw erect let's use graphics style painter paint button and that would be the rect our palette the button style which will be uh cool bar excuse me um oh pressed state i guess we have to keep track of this type of stuff suppressed item hovered item checked enabled is i guess if the widget is enabled and focused um let's just say never focused why do these have a focus focused what does a button being focus look like oh right right yeah it's not relevant for cool bar but it is relevant for for regular plane buttons because they get a sort of a blacker thickened outline in that case but that's not relevant right now so we want uh pressed and hovered okay um great and then we're gonna need some mouse events so mouse move event and oh you know the usual miles up miles down um okay so those and we probably want hover and um leave wait what does hover event get i feel like that no no it's not hover it's enter event enter event and it's just a plain core event okay enter event core event yeah it's it's been a while since i made a new widget as you can tell i don't even remember the names of the events um but this is going okay so far okay so that's that's a quite a hefty chunk of event code that we have to implement but nothing that we can't do so uh i'll train [Music] okay and event um yes actually we don't need those so now we need a way to figure out the item under the cursor so let's have a way to grab at that something like item at um at a point so let's implement that it's a private map oh of course um okay so the simple way to do this is just iterate through the items and then say if item wrecked for this tray contains the position then return the item otherwise just return null footer okay so if auto item is item at um oh wait uh we can't use the enter event because we don't know it's kind of a flaw of the enter event actually that the enter event the enter event should probably be either be a mouse event uh or be an enter event so that you can give coordinates um because i can't there's nothing here that tells me where which item you're you're entering over so we're gonna have to wait for the first mouse move actually although i think that will always come so yeah that's not the end of the world although it seems like it would be nice if if the enter event had coordinate information um so let's see then like item hovered is true something like that um if item is not hovered uh then that then we need to update wait if item is hovered then we need to return because it's a no op that's what we need to do otherwise do that yes that's what we'll do um that's a shadow um maybe we can write this in a slightly nicer style um i don't feel bad about shadowing that can you turn off the complaining about that because we allow shadowing um wait okay i i don't know can i how do i turn that off um shadowing names from outer scopes go away did it go away i feel like that didn't change it um maybe i didn't do it right wait is this about this python it's some python thing okay well that's that's my bad um shadow was that a compiler complaint or something we're not dealing with shadow though okay let's not get distracted i will just tolerate that um okay hmm this i guess the more i look at it the less i like it because it looks so ugly that we're using the same name so dang it let's say hovered item then maybe maybe it had a point [Music] so let's see mouse down event mouse up event uh when we get a leave event what we want to do is we just want to iterate through uh everything and make sure that nothing is hovered uh and then we can do an update just to be sure okay let's see how this works oh mouse events of course enter right we decided against that but let's make sure that we have uh some text on these things so if we have a if the item has an icon also maybe we should just call it bitmap um okay c-line i'm not going to let you do that because i can tell that you're overreaching and trying to rename unrelated things so sometimes sometimes the rename usages thing in c line gets a little bit overzealous and renames unrelated things which can be a little bit frustrating [Music] but that's okay we can do that by hand it was a tiny rename anyway so if we have a bitmap then painter draw a bitmap and we will draw it where exactly let's just do it at the wrecked location for now um oh shoot this is for literal bitmaps i guess what we want to do is blit um i want to blit the item bitmap item bitmap correct uh no opacity or alpha to worry about and then we want to draw some text to draw a text correct um center left okay i think we're gonna have to break these rect out here actually so interact icon wrecked that's gonna be at the um leftmost part of the widget so the wrecked x rect y um and then i guess item bitmap um let's say let's say a 16x16 that should be fine so we'll draw it there and then we will derive the text rect from the icon rack so the text rect is starting at iconrack.right plus some cool number and the item y and then the width of that is um whatever remains actually so i guess we can do something like this followed by uh react.height followed by since now it will overlap too far to the right but instead of arithmetic arithmetic we can also just intersect it with the item racked which will cut off the slack on the right hand side and then we draw a text into the text right so that's direct followed by the string followed by some font text alignment text alignment center lift color so graphics color um palette color tray palette color graphics color roll tray text um okay that was uh just a little bit of um programming there let's see how that looks in practice and that's terrible not terrible indeed um it's a start it does look a little bit awkward with that button color actually um button style is button style tray wait what does that mean button style tray what was that achieving previously that doesn't mean anything um what the heck okay so wait what if i just pass trey here then what does that do though i guess trey just means not cool bar somewhere um oh i see so it makes it not fill in the background that's pretty good so we need to center um those icons vertically and also that's not enough padding there so let's just add a little bit more padding uh six and we will start the icon rect add a little bit of an offset so uh plus two y plus two and we'll say icon rack center within racked center vertically within actually which means that we can just ignore the y value um [Music] yes what will that do okay so that black outline is screwing everything up so let's get rid of that so we can see what it really looks like hmm okay it's pretty decent there's still yeah we need to like yoink it to the right still um so we need about four here and the distance from the icon to the text seems good-ish what if it had two more pixels though what if it did okay hmm yeah that looks a bit more like it uh and then if you mouse outside of this we should unhover also it's not just when you leave the entire trade widget area but like also when you go below so uh that would be in the um mouse move event if there's no hovered item then do this um if item is hovered then we'll need an update yeah so if something was hovered and we now have nothing hovered we will update the widget ah damn it why do i i keep starting the wrong app um okay so there we go that's pretty good and then we want these things to be checkable and uh clickable and all of that stuff so let's see about checkable actually we did didn't we do the item check thing already said item checked update um does that actually work though set item checked let's see if somebody's actually trying to set them in the tray here so set item checked index it should be pretty straightforward just to find out if somebody's setting it so that we know that the vlog is in the tray class because i feel like it should be showing up in a checked appearance state so they're all being set to true [Music] well that doesn't seem right now they got all got set to false what what stupid nonsense did i do set item checked is being called with a bogus value because um wait what model root path is location button path um okay well we're doing some nonsense here when we're iterating so iterating irritating um index is this root model root path as this and the location button path is this so location button tray item index root path and location button path hmm what are we screwing up um moderate hormone wait why is the location button path holding on for all of these things that doesn't seem right common location buttons append path index uh oh shoot path is the wrong thing here location.path that's what we wanted okay yeah that explains it so i guess we again we had a shadowing local earlier right yeah there was a shadowing local here called path um before we changed this and then we got rid of the local and then it was using the outer path so it's funny how i keep encountering reasons to actually care about shadowing locals very interesting okay so set item check true down so the desktop should be set to true and you go to um downloads and then downloads should be set to true set item check true i wonder why it doesn't appear that way um in the thingy let's have a look at paint button item pressed hover checked so it seems to me that this should work but maybe i'm screwing something up here we're just going all in on printf debugging today it seems like so um yeah let's just printf our way to success if you click on this boy clear clear four oh shoot wait clear item index ah that's the problem ah was not using item index i was using the argument at the parameter index okay well that was a stupid bug okay let's see if we can have a little bit less silly bugs so that's fine yeah see now it looks checked when that's the active directory switch to downloads poof downloads is now checked of course we can't click on these puppies yet um [Music] but they automatically move around that's pretty cool yeah root right there and if you go somewhere that's not represented nothing is auto checked great so then uh what do we need to do now we need to make these things clickable so let's say that when you press down on one of these things with the mouse down event so um pressed item is item at event position if if there is no pressed item uh also if the event um button is not gui mouse button left yes if it's not a primary mouse button click then screw it if there's no pressed item screw it but if there is one pressed item pressed is a true um yes so it's being pressed although yeah now is like the this eternal thing of like when something is being pressed you still only want to consider it pressed while the mouse is within the widget rectangle so an example of that is like if you use the cancel button here right if i press on that it gets a depressed state but if i move the mouse cursor out even though i'm still holding the mouse button it it now looks unpressed [Music] but moving the cursor back in makes it look depressed so um also depressed is such a weird word um probably should just be called pressed let's let's not say depressed because it's sounds so confusing it sounds like it should mean unpressed but whatever so let's call it pressed and unpressed so we have to remember that the rect matters so pressed is true but in order to display it with the pressed state [Music] we have to uh let's see we have to keep that in mind here i guess pressed and hovered maybe we can just do this pressed and hovered because if you're pressed and hovered that means that you should be rendered as pressed okay so pricey pressy hmm although wait that's confusing oh right we we don't have the logic to let go uh for press we also want to offset them a little bit so we want to do that thing where um if it is pressed let's see how do we do that if item pressed then icon wrecked translate by one comma one so we just you know shift it one way down once one pixel down and to the right um for that pressed appearance and then mouse down it needs to have some corresponding action in miles up so that if you let go so let's see if you let go and um and uh let's say actually we can we can do this thing right here so pressed item is item add position uh if not pressed item well we still have to clear the pressed flag actually so let's see if not pressed item if pressed item pressed item and pressed item pressed then on item activation now we activate the item so pressed item custom data okay and then we want to clear all of these flags so item pressed false update all right yeah so this thing becomes a little bit update heavy uh but it's not such a big deal and we can certainly put more effort into reducing the number of paint things uh also that looks really weird because we need to also shift the uh we need to shift the text as well but that actually gets a bit confused because of the relationship between those two things so um let's see where did we do the icon react so maybe we should compute the text track and the icon react at the same time here and then text correct translate by yeah so it's a little bit awkward but at the same time it's okay i think we can get rid of this now okay cool cool oh it should stop looking that way if it's not hovered so item pressed and item hovered yep that will be better oh yeah now it's starting to look all nice and natural okay all right and then i think we want to do bold text for the um for checked items something that we had before so uh let's see item checked if so graphics font font database font database default font default bold font no no wait that's not what i want to do i want to do font [Music] i want to get the font of this thing get a bold variant for it can i do that um oh right of course so this font or this font does that work that's the const reference that's a const reference that's also a const reference then we just have to pass in the food that's not where that goes how about there okay so yeah so we pass in a bold variant of the font for checked items let's see how that looks okay looking bold and beautiful it's a little bit of a clip there i think we went a little overboard on the padding actually it doesn't look particularly good you can see here in particular um there's too much too much horizontal padding so let's scale that back to um i don't know let's get to magnifier and take a look-see yeah i feel like maybe even five so cut three pixels off that see what we feel um it looks a little bit cramped all of a sudden but maybe it's okay maybe that's okay yeah i mean we can always tweak these things that actually does look pretty nice okay so now that we've done all this what is the state of um tabbing around right so tab goes to here from here we tab all the way up there and down here okay so now we're just cycling through these things and this thing doesn't get any tab focus at all so all the stuff we've done so far we could have achieved by just making none of these focusable um but i think it would be nice if you can get here with the keyboard so um [Music] let's make it focusable perhaps uh we just have to figure out how this should work so the tray should have a focus roll so set focus policy rather gui focus policy tab focus okay and then if we have focus we need to uh react to some things so focus event i forget what the argument is focus event yeah seems like it uh and it's probably called something else like focus in yep focus in and we have focus out okay and then we also need to listen for um key down events okay so what are we going to do in these events well um let's see let's see what we do here so um when we gain focus i'm thinking maybe the easiest thing to do would just uh share the the logic between hover and um [Music] and keyboard navigation so we would say something here like set set item hovered zero so if m items is non-empty then set item hovered zero yeah we can do that and if you leave then we can just say um said nothing covered i guess so yeah we we need to implement this so if it's empty return otherwise um items zero hover true okay that seems like a reasonable behavior and let's try it out and when you focus out we just clear everybody so like that basically yeah again like over eager with the updates and repaints but that's fine for now and then when you key down um if event key is um key code key down if event modifiers so there are no modifiers let's say key down and no modifiers um and also if m items is empty then return of course so if you key down then you want to switch to the next thing so if we have a harvard index [Music] so let's see actually we can do this um item hovered then also the way that we do the hovered thing is really weird because why am i storing this on each one of these instead of having [Music] instead of having something like this right so like m pressed item right so because then i don't have to loop through all the time i don't know why i didn't do it like this this is it's clearly nicer so let's actually go with that checked item index uh let's get rid of those and then um covered item indexes nothing hovered item index is zero sure uh key down event so um m harvard item index is uh hovered item index value or oh no no hold on if we don't have a value so nothing is hovered then the first item becomes hovered otherwise when you press down we just want to go to the next item uh and if that loops around then we need to um we need to skip back to zero yes okay and then update like what bookmark i don't want to bookmark okay and now we have the change of fujilion things but that's okay uh hovered index is index wait no no that's not at all checked only one thing can be checked because it's exclusive right so there's only one of each of these set item checked hmm so if we're doing a checked set then checked item index becomes index if we're unchecking if m checked item index is index then checked item index becomes nothing and then we do an update let's say yeah uh and then is pressed is pressed item index is hovered yeah yeah yeah this will be so much nicer what the heck was i thinking but this is also a good example of why like you need to keep reevaluating like am i writing this in the in the nicest way possible and we were clearly not doing that so uh it's a good thing that we didn't just continue with that bad abstraction because all of those loops were really ugly but um i was just accumulating irritation and then finally realized what we should be doing okay so what's even going on here right uh this is clearly um ugly so if you're hovering nothing then what we do is that and then update actually here we can be a bit nice and only update if we had a hovered item yep and um how about item index is covered item index yeah um something like that okay [Music] okay leave event all these loops go away because how ugly was that holy moly and we are back okay so much nicer uh we should also handle ma key up obviously so key code key up with no modifiers uh if you don't have an item selected we can take zero [Music] we could also do items dot size minus one take the last one if you key up from nothing might be nice starting from the bottom and outside of that we will just say um [Music] let's see so if you don't have a value or have an index item value is zero um then we loop around yeah otherwise we will simply minus one and then on mouse down um [Music] we want to do kind of the same thing except um we wanna well we wanna activate sorry um if you key down the enter key so if event no modifiers and event key is key code key return and we do have a hovered item index and let's activate it so m items covered item index value custom data i wonder how that will work in practice i guess we'll find out oh look at that so now i can move these with the keyboard and enter activates so i can just up and down these guys right but if i tab wait where did my focus go oh shoot wait this thing swallows the tab oh wait why does that happen because i have to ignore the key event i guess for tab um [Music] wait how does this work again key is the event the event needs to be ignored if it's a tab press so if event key is key code tab um and then that will allow it to bubble so that somebody else can take it and cycle to the next widget um i think that's what we're missing i forget if there's like a nicer way i should be doing that i don't recall wait why am i stuck in the switch hmm that's so weird wait where do we implement the dang tab navigation next focusable focus next a widget this is done by widget keita oh shoot shoot shoot i am not uh calling the bass class like a total doofus um we definitely need to do that so uh da da let's um let's just call to base here that's what we should be doing so our base is frame so frame key down event event and then if the event has been accepted um we do no further processing yeah yeah that's the nicer way of doing it because if if our base class like gui widget wants to process this thing and implement tab around yes exactly okay that's really cool so let's see about system server um and bring back those rectangles gui focus debug is one and then let's see how this looks um if we can actually see that beautiful rectangle moving around okay so we're here oh and because nothing is selected i can't go to open okay so um okay so open cancel now this thing is focused now that thing now this thing not that thing okay yes this is a very reasonable rotation i feel um and this thing actually cooperates with the hover state so if you press up and down it doesn't do anything wait why doesn't that work oh damn it now the wait that swallows the keys um wait do we have to do this before we do that oh that's kind of messy hmm no that's not very good wait how do we factor this [Music] so maybe what we want to do is this and then call to base at the very end if we didn't do anything with it then we will call to base um but i still want to do an early return instead of nesting so let's do that so here we will simply call to base and we'll also call to base if none of those branches worked out hmm okay that's pretty nice um i guess or event has event modifiers non-zero if it has modifiers we don't want to we don't care we only want like key down key up or enter we don't care about like control up or control down stuff like that um yes what did i do this time was that you okay okay let's try oh i have to focus the thing ah my keys wait why don't you work now oh if if items is empty okay well now i'm just being um sloppy so let's unslop and finally test this thing okay so we're back in business uh and this works nicely yeah as this is such a weird little thing but i remember um many programs working this way like sharing the logic between hover and uh keyboard navigation so i think that's uh that's makes sense to me um it is a little bit weird like if um like you have you've focused something with a hover it looks like it's hovered but it's actually not under the cursor but it's still it still makes sense to me yeah we can tab out of it so wow that was a lot of work to achieve uh this thing but i think the system will definitely feel better if you can navigate it this way so that it doesn't swallow five tabs for that for the tray ui element for the trade widget rather the tray now acts as a single widget that has internal navigation uh i think that is super sweet um and then now notice that we also previously we had focused rectangles these dotted rectangles around each piece of text we don't have that anymore but i think that's okay um oh this thing doesn't focus when i click on it but that makes sense to me i think it should only focus when you tab to it because if i'm clicking on this i'm i don't mean to click here and then start doing a keyboard navigation within this widget i'm just using the mouse to open something quickly um so i think it's fine for this thing to be tab focus only it makes perfect sense to me yes yes i like it very much so okay let's um commit some things so we'll add the tray um wait let's see this thing can definitely be improved um it's certainly over eager with painting and i do feel that some of the logic came out a bit clunky set item checked wait did i end up not using that no i am using that wait why don't you oh it looked it was it was unused for a moment there but no okay never mind um gooey gooey tray um a um [Music] dedicated widget um for a four four four four oh what do we call this three tray for the file picker um common locations so um the this file picker the dvd uh file picker implement has implemented it's common locations tray as a [Music] composite widget um built from a gui frame with a bunch of gui button inside it um the the problem with that is that it creates a long and annoying chain of tab focusable widgets or let's call them keyboard focusable widgets this patch adds gui tray which is a dedicated single widget that implements the same ui element but without child widgets so so it's a little bit it feels like a little bit sad that we have to resort to a custom widget but at the same time it's like 200 lines it's not the end of the world um plus header if there's if there is a good abstraction that would allow us to compose this instead of having a custom widget um that might be interesting but either way i'm it is also nice to have a dedicated widget for this because it means that we could use it elsewhere uh there are other locations where i would like to use a tray or experiment with a tray i think it could make for example a pretty sweet or it might make an interesting category selector for something like a settings window because in the settings window you can you can use um tabs but you could potentially use a tray solution instead um it's something to experiment with i'm not exactly sure um i get i guess what i'm imagining is if you have like a hierarchy of settings where you have like um vastly unrelated settings at a high level and then those those could be represented by items in a tray and then within each tray item you could have like a tab widget with more closely related settings but anyway that's that's not what we're doing today so that's just something i was thinking about trays for um yes and then let's use the tray in our friend file picker use um the new tray widget in uh file picker let's say this yeah this um this remove this this shrinks removes um [Music] all the extra entries from the keyboard focus chain and makes file picker actually pleasant to navigate with the keyboard i have to say it is actually kind of nice now okay uh let's see so if i have something selected how many taps to get back one two three four five six six down from sixteen uh i would say that's a huge improvement so um that is pretty good should we also hide selection in this thing when we're unfocused hmm actually now i feel conflicted because if i switch the window we should probably still show the selection because now i can get back to it but if i switch to a different widget we shouldn't show it um that's a problem so wait so it's focused is that an overloaded concept perhaps that i'm forgetting is focused also implies that the window is focused if the window is oh it has to be active to be considered focused shoot um [Music] okay maybe i um damn it let's see let's undo that change uh oh wait oh let's undo that change the text editor selection unfocused and then let's look at what we really wanted to say so physical line has a selection so is focused is hmm yeah so what we really mean is that if the window is not focused then we still want to paint it if as long as it's the focused widget within this window so window is active let's say is that what we want and then we want to gate this on uh window focused widget is this so that's actually a little bit convoluted but [Music] but i think that actually will give us a nicer looking behavior no not browser okay wait so blah blah blah click over here now chosen gray that's good reactivate it's fine click on this thing now this guy is not showing up click over here yeah tab tab wait why did that clear out there well that's that's not related to any of this um oh man this just makes so much sense um yes see you can't go there you can go here yeah it becomes gray but yeah so these guys probably shouldn't or should they um that i'm less sure about that one because like the selection here is still relevant even though it's not focused right because i i go here and i start editing this actually if i do that maybe this thing should unselect oh there's so much stuff to do here let's not get let's not get distracted let's just finish up what we were doing so um let's see so git commit um don't paint text editor selection um and non-focused widgets [Music] if the text if a text editor is not the focused widget in its window [Music] don't we no longer paint its selection um there was no um hmm what the heck did i say before yeah that's what i wanted to have showing the selection in non-focused text editors was not really useful since refocusing the widget would clear or change the selection immediately anyway um note that inactive windows text editors and inactive windows can still be the focused widget within that window and will still be painted um with an inactive looking selection yeah yeah there's probably more issues in that area but i think i think that's it uh for today so pretty good stuff uh some nice um gooey polish work uh just improving stuff for keyboard navigation something that it always feels like a good investment because uh i'm certainly a heavy keyboard user and um the system is skewed a bit towards or biased a bit towards mouse usage and making these convenience quality of life improvements for keyboard just feels like a wholesome and nice thing to do so yeah that's it so thank you very much for checking out this video if you made it here then i hope it was interesting and um i guess i will see you in the next one so bye
Info
Channel: Andreas Kling
Views: 7,114
Rating: undefined out of 5
Keywords: serenityos, c++, programming, osdev
Id: 1csOEl18BJU
Channel Id: undefined
Length: 114min 9sec (6849 seconds)
Published: Thu Oct 21 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.