Lemur

I cant believe how well designed this is, pure genius.

5 Likes

Wow! Thanks so much for the kind words. (And I guess you guys are lucky not to have seen the years of failures before I got there… lol)

1 Like

@pspeed Paul - have you considered live streaming when you work on something? I should be interesting to see your thought process.

1 Like

Live steaming can not capture @pspeed’s 4 dimensional thought process

4 Likes

There is probably more truth to this than you know.

So, the funny thing is that most of my thought processes are all written down. (or are intrinsic to my experience/skill/etc.)

…when I have to design something that isn’t already fully formed in my head, I open up a text file in my programming editor and have a conversation with myself about it. At some point, the design reaches critical mass and I move to code. Sometimes I prove myself an idiot, delete the code, and go back to the ‘chat’. I try not to post them because it kind of taints the process to think I might post them. (Sort of like if you knew there was the possibility of posting your diary entries then you might not be as honest in your diary, etc…)

Once I release some of the moss stuff then maybe I’ll post some. They wouldn’t make much sense now… I’ll see if I still have an Lemur related ones laying around. Though Lemur came mostly fully formed as it was the second/third iteration of something I started in the Mythruna codebase and so just applied a lot of hard lessons learned to a new clean version.

2 Likes

I found some of the Lemur design discussions. As I thought, most of them happened after core Lemur had been around for a while because it was mostly born fully formed.

WARNING: these are basically unedited and direct views into my thought processes. There will undoubtedly be a lot of stupid stuff in here and that’s part of the process. I learned along time ago that deleting the stupid stuff is a sure fire recipe for having to rehash the stupid stuff in a month when you have that stupid “bright idea” a second, third, or fourth time.

Here is one from when I was trying to work out some matching issues in the style selectors. It’s about as meandering as you might expect… and I’m only 90% sure the code reflects the final conclusions. Sometimes I go back and notate what I did and sometimes I just leave the file hanging.

styles.txt

So, styles have been in and working pretty well so far but
I'm bumping into an issue and it may lead to a refactoring.

If you have something like a button with elementID:
slider.up.button

Then you can get 'containment' as "slider", "button" but
you cannot get containment as either "slider.up", "button"
or "slider", "up.button".

This wasn't a big deal before because I was using the style
part incorrectly but now that I'm giving things useful element
IDs, I run into issues.


Right now when a style attribute is registered it gets broken
down into its various parts, to include various combinations
of containment.  But the containment is only operating on
the raw non-dotted parts.


And as an example of precedence just to keep things in focus:

slider, button { color:blue }
thumb, button { color:red }
 
A slider.thumb.button would be red because it is more specific.


I guess we could keep things in a tree.
slider {
    button {
        color:blue
    }
    thumb {
        button {
            color:green
        }
    }
}

thumb {
    button {
        color:red
    }
}

...then whether we are running backwards or forward we can traverse
the tree.

slider.thumb.button... start with:
slider
    -look for thumb
        -look for button
    -look for button
thumb
    -look for button
    

The problem is that we now apply things in the wrong order.  In this case
we should see blue, red, green.

What if the tree kept it's wild card splits?  Actually, we'd have to
do things properly.  "slider.thumb", "button" is not the same as "slider", "thumb.button"

So...

slider, button { color:blue }
thumb, button { color:red }
slider.thumb.button { color:green }

slider {
    * {
        button {
            color:blue
        }
    }
    thumb {
        button {
            color:green
        }
    }
}

thumb {
    * {
        button {
            color:red
        }
    }
}

If we do a breadth search using our position in the "slider.thumb.button" as
depth?  I'm still not sure it works.

What if we invert the tree:

button {
    in slider.thumb {
        color:green
    }
    in slider {
        color:blue
    }
    in thumb {
        color:red
    }
}

Actually, it might be something like:
slider.thumb.button {
    color:green
}

button {
    in slider {
        color:blue
    }
    in thumb {
        color:red
    } 
}

Still, it's some kind of combinatorial problem because we'd have to potentially
check all paths.

Well... except that the trees will only ever by two deep, right?

Let's say we do loosen up the 'slider.thumb.button' to a containment
relationship:

button {
    in slider.thumb {
        color:green
    }
    in slider {
        color:blue
    }
    in thumb {
        color:red
    } 
}

So we still can't do things like direct map lookups, we have to check
all of the children at a particular level.  But it's still better than
running through every key, I guess.

How do we efficiently let the "longest match" win?  What if we hit
button { thumb {} } first?

Also, we still need to chop down the ID at each phase so that we can
(eventually) catch the slider.button, too.  Unless we do something more
complicated than string chopping.

We need to potentially hit all of the children but we want to keep the
matching ones in precedence order.

If we are doing only two level containment then I'm not sure it matters
which end we work from.  Presuming we have a valid way of calculating
precedence then it shouldn't matter.  That's key though... which way makes
that easier?

Let's flip it around again for a sec:

slider.thumb {
    button {
        color:green
    }
}

slider {
    button {
        color:blue
    }
}

thumb {
    button {
        color:red
    }
}

Hmmm... note that we also want to find each element along the way...
so "thumb" by itself, "slider" by itself, etc..  Probably even
slider.thumb by itself.

we could use regex rules and then cache results for specific IDs.
...clearing the cache if the attributes are changed.

Maybe need regex like 'rules' and meta-rules (composite rules for
contains(key, key) style)... then sort the matches based on 
tail to head completion criteria and gap size.

The first one (starting from the tail) to hit a wild card first loses.
If they both hit a wild card in the same place then the first one
to hit a non-wildcard wins.

What about ones where the tail was left off.  It doesn't quite work
out.
list.*.thumb
list.slider.*.button

...trying to match list.slider.thumb.button





I wonder if we could do depth first recursion from the tail.  Keep
going until we stop finding matches and that's the "tail ID" for
containment in that portion of the tree.  Then we wild card until
we find a match and do a similar depth traversal.

When we pop back out then the next traversal will be less deep
and less specific.

foo.slider.up.button

foo {
    up.button {
    }
    
    button {
    }
}

Whether we do it as a double-key thing or whatever, I think it's still
a valid approach.

Start with "button" and we find both "up.button" and "button" but we'll
traverse up the first "button" and find that "up" also matches and so
keep going up that line first.  Only when we've resolved it then 
we try the next one.

The tree really should be upside down then.

button {
    up {
        * {
            foo {
            }
        }
    }
    
    * {
        foo {
        }     
    }
}

Tail-traverse until we hit a wild card then mid-traverse from
there all branches that match.  There is an implied wild-card
at the root or we otherwise just cap it off when we get there.

And here is the one on key navigation which has been especially useful because I can reread it when I come back to it to try and pick up where I left off:

key-navigation.txt

Trying to work out global UI key navigation.

Here is something I typed up where I'm hitting the issue.

// Really we'd want to focus on the port text field.
// Perhaps we could have "Focus" events or something.
// Somehow need to indicate "focus on this component" and
// have the view code be able to react.  Though, in fact,
// this maybe should be automatic in key navigation... enter
// or tab by default would go to the next focusable field.

I feel like if I had key-nav working that the "enter to go
to next field" thing would be more obvious.  Though it is
something to keep in mind while I design.


The high level goal is that arrow keys, tab, and shift-tab should
navigate around the UI.  At the very least, tab and shift-tab with
left/up and down/right being mapped to the same action by default.

...with the ability to easily override the default up/left/down/right
behavior for things like grids, tables, list boxes, etc.

(That's kind of a tricky one.  How does a list box deal with up/down
before the focus navigator does it... or are these sorts of 'functions'
global and you can tap into them if you like?  Well, yeah because 
joysticks may map to them also and so on.)


What this means is that keyboard navigation is really InputMapper-based
navigation.

I think there are two layers.  One layer is the focus management part
which is only barely fleshed out.  The other layer is how input is
mapped to default focus movement.

Need to look at Swing again to learn from their 'experiences'.


Random things as I read the swing docs. http://docs.oracle.com/javase/tutorial/uiswing/misc/focus.html

So perhaps some separate focus manage thing that maps input to focus
and can be turned on/off as needed.

Nested focus.  Containers may want to indicate that they are focused
even if it is actually a child that is focused.  A "child has focus"
concept should be included so that root level windows can know this.

Perhaps need an easy way to reset the focus of the default child.  Say when
popping open a window for the second time we may want to automatically go
back to the originally focused component.

Swing 'focus' really only deals with "forward" and "back".  This probably
works ok for us, too for the most part.  Though in our case we will be
using cursor keys as well as "tab" and it may make sense to have a concept
of left/right and up/down, also.

In Swing, components can override the traversal keys.  For example, an edit
field can allow tab to be entered into the field instead of traversing to
the next component... and then ctrl+tab moves forward instead.  And even
if we don't want to support tab that way, left/right would _definitely_ need
to be overridden.  In our case, this is kind of up to the edit field, though
because it will be getting keys before anything else.

Swing components get a chance to do stuff on focus lost so they can validate
and so on.  Being able to listen for focus changes at the component level
is needed.

"For a component to gain the focus, it must satisfy three requirements: 
it must be visible, enabled, and focusable."

Enabled state is something we need to keep in mind... and "focusable" may need
to be a property and not just a tagging interface.  ie: being able to enabled/disable
'focusable' is useful.

Swing components also specifically focus themselves when clicked on.  If they
don't do this then they don't get focus.  So our text edit field is fine already
regarding that.

Thinking we may want "major focus fwd/back", "minor focus fwd/back", and perhaps
another one to be the equiv of ctrl+tab.  Or maybe up/left/forward/down is good
enough granularity plus another next/previous that works at a higher group level.

In Swing, components can be removed from the focus cycle simply by calling
setFocusable(false).  Useful.

Swing has a focus traverser policy that can be set per focus root (a JFrame for
example) that defines the next/previous order.  This can provide first/last and
next/previous components given some other component.  Oh, also a "default"
component which is the first one to get the focus... which may not be the same
as the first in the cycle.

In Swing, Focus listeners can be registered per component.  You can also listen
to all focus changes with a property change listener on the keyboard focus manager.
I think we already kind of cover that case... or at least have a central place
where we could.

In Swing, when a component is disabled, focus is automatically transfered to
the next component.

Interesting tidbits here:
"Requests that this component should get the focus. The component's window must 
be the current focused window. For this request to be granted a subclass of 
JComponent must be visible, enabled, and focusable, and have an input map for 
this request to be granted. It should not be assumed that the component has the 
focus until it fires a FOCUS_GAINED event. This method is preferred to the requestFocus 
method, which is platform-dependent."

Things I've teased out of interest:
-'must have an input map'... ie: the component uses the input map to manage next/previous
    and so on.  It is not a default global thing but controlled by the component
    itself.
-FOCUS_GAINED event... a way to see when the actual component has focus, ie:
    we need to make sure we don't fire similar just because focus wants to change.

In Swing, the keyboard focus manager does define default keys... but I suspect these
are just used to initialize the component's keys.


Need to think about how components handle component-specific key mappings... and it's
not just keys but InputMapper functions, I guess.  Or there is a way to map InputMapper
functions to component input actions.

Swing has InputMap:
"InputMap provides a binding between an input event (currently only KeyStrokes are used) and 
an Object. InputMaps are usually used with an ActionMap, to determine an Action to perform 
when a key is pressed. An InputMap can have a parent that is searched for bindings not 
defined in the InputMap. "

This is the indirection from input to "logical action name"... which is then used
to lookup an action.

In Lemur's case, we have InputMapper which is already doing logical mapping but it's
not doing it per component.

Do we need something where a component can add/remove mappings based on focus?

How do we deal with input precedence?

Here is an example:
Maybe we have a custom component like a spinner (or even a slider would work) where
we now want +/- to increase and decrease the value.

How do we do that in Lemur?

One way: the component registers increment and decrement functions and groups them
appropriately.  Then it enables/disables that group based on focus.  This is not
too tricky or onerous on its own.

I guess it would be up to the application to disable anything it had mapped to +/-
already.

There could even be a default group for 'focus navigation' in general.  One thing
InputMapper doesn't provide (and maybe it should) is groups of groups, ie: group
hierarchies.  Even just a flat group sets concept might be enough.



So if we make some focus manager thing... what does it interact with?

InputFocusManager
...or...
InputFocusState?

Lemur already has the concept of a central focus manager and it deals with
controls that implement FocusTarget:
public interface FocusTarget {
    public void focusGained();
    public void focusLost();
}

// Note: need to add isFocused() probably  Something that returns true if
// a FocusTarget or any of its parents are focused.  So its probably Spatial
// based in that case.

// Probably should also add isFocusable() which may change at runtime but
// is generally setup as part of the component.  Because, for example the
// GuiControl is always a focus target but may not always be 'focusable'
// from a focus traversal perpsective.

GuiControl implements this and passes it on to any components that implement it.

The "currently focused component" seems to be something pretty well defined
at the moment.

What we are actually talking about now is how to implement focus traversal.

Given some component that has the focus, how do we go next/previous and so on.

We could have a FocusCycleRoot interface that we could do a similar lookup to
FocusTarget and then traverse up parents.  

So this interface would be responsible for telling us what the next/previous/left/right
focus targets are.

FocusCycleRoot {
    first, last, next, previous, default, initial?
}

If we want to have left/right/up/down then we need to think of what
that means for home, end, and so on.  It gets a lot more complicated
really fast.  Like, what do you do when you go right at the end of a line?
"down" + "line home" might take you back to the same line you were already on.

In fact, swing UIs only use tab and shift tab for this movement.  We can
add some actions to buttons to allow arrows to move around also.

Some other property that can somehow trap the regular next/previous focus
in that container.  Think root level windows and such where special action
must be taken to jump to another window. 



Focus Containment
------------------

Somehow we want the whole container hierarchy to know that one of its
children has focus.  I think the main focus manager could take care of
this but we need to be careful not to have default focus processing
reset the child focus.

I guess we'd need to keep track of the whole hierarchy and only
deliver the focus lost/gained to changes in the hiearchy.



Steps:
---------
DONE 0) Make a focus package and move the focus-related stuff there.
DONE 1) add isFocused() to FocusTarget and implementations
DONE 2) add isFocusable() to FocusTarget and implementations  (easier and
    cleaner than adding a FocusTraversable interface, I guess)
3) modify FocusManagerState to deliver gain/lost focus events to
    the whole hiearchy.
        Seems to be DONE
4) create InputFocusManagerState that takes the FocusManagerState
    instance and InputMapper instance.
5) create a FocusCycleRoot interface and default implementation.
        Seems to be DONE

...the thing is... how does that work?  GuiControl can't just
implement FocusCycleRoot.  Some gui elements may be cycle roots
and some not.  Ah, perhaps FocusCycleRoot is a separate control
entirely.

Containers can then add that control... or a default implementation
of one that defers to their layouts.  And we can do that based on
a setFocusCycleRoot(boolean) property on the Container class.

It's nice and clean and completely separated.

    
 
2015-02-27
==============
I'm trying to pick this up again and I've kind of lost the thread.
Right now, I need to know when an text field loses focus so that I
can 'commit' it's value.  It's also getting really annoying that
tabbing doesn't work.

While I'm here, I thought I'd at least look back at making focus
traversal actually work.  So that a grid of buttons could be
navigated... at least with up/down tab/shift-tab.  Mappable to
joysticks, also.

Let me see where the uncommitted code left off...

So, it looks like I implemented a lot of the stuff.  There is a
FocusCycleRoot control and default implementation that knows
forward, back, etc..

Focus manager delivers to the hierarchy, and so on.

There aren't any ways to generally register focus listeners.

There is also no way to map keys to next/previous... next and
previous stuff is basically not used at all. 

..4) create InputFocusManagerState that takes the FocusManagerState
    instance and InputMapper instance.

...except I no longer know exactly what I meant there.

Note: we could use the action map idea... the issue is that it
doesn't support function style mapping like InputMapper.  So it would
be hard to map joystick movement, etc..  TextEntryComponent already
has this issue.

So, we'll have some kind of focus functions.
F_FIRST, F_LAST, F_NEXT, F_PREVIOUS

Yeah... so these will grab the current focus cycle root and call
things appropriately.  They will be part of a FOCUS group and so can
be globally enabled/disabled.  And there is no reason that individual
components couldn't add whatever crazy functions they want to... that's
a separate thing, I guess.

So the InputFocusManagerState I mentioned above, registers a listener
for those function IDs and simply moves stuff around based on current
state.  TextEntryFields completely take over all key input so it makes
sense that they are different.


Note: buttons then also need a way to indicate that they are focused.
    ...which is a whole other can of worms...
    
    
Focus listening
----------------
There are potentially two kinds of focus listener... one that 
wants to know when focus changes globally and one that wants to
know when a particular component loses/gains focus. 

Should all focus targets then be listenable?  No, I don't think so.
A TextEntryComponent shouldn't have to manage focus listeners just
so that it can be tagged to get focus events from the GuiControl.

FocusEvent {
    from: FocusTarget 
    to: FocusTarget
}

FocusListener {
    focusGained(event);
    focusLost(event);
}
   

I must confess I picked up this habit from you. While I’m writing it I often see flaws in my logic, but instead of correcting my notes, I write why it’s flawed, so that if I come back to it later, I can see that I tried a certain approach and it didn’t work. Pretty handy when you think you had a light bulb moment, only to realise you proved yourself wrong already.

1 Like

Exactly.

And imagine coming back over a year later and getting to ‘catch up’ to where you were. Vital.

Having the skill set where you can implement the thoughts is what I find awe-inspiring.

I don’t know if I have the time left to actually get mine honed enough to even reach a quarter of what you guys are doing.

When reading the forums I am amazed by some of the things people share and I wonder at their abilities.

Forgot to mention, @zissis port of miglayout when combined with lemur is such a perfect match.

1 Like

I use it extensively in my game. It was created out of necessity and allows you to rapidly create complex Lemur UIs with ease without giving up anything Lemur has to offer.

It would be nice to integrate it into lemur layouts so don’t have to have separate class.

Problem is that @pspeed is not a MigLayout guy so it would be difficult for him to maintain. At some point I need to get off my ass and set up a Git repo for all my work.