[V2] Trying to make a class-based modular UI

Discuss the future of the AutoHotkey language
User avatar
evilC
Posts: 4824
Joined: 27 Feb 2014, 12:30

[V2] Trying to make a class-based modular UI

10 Jun 2014, 17:17

Hi all,

I have decided to see what AHK V2 can offer me, so I thought I would start trying to lay the groundwork for my all-encompassing master plan ;)

What I have in mind is an AHK script that gives you the power of AHK's hotkey system via a GUI-driven interface.
The primary intended use is for games and I am looking to basically replace the software that came with (or failed to come with!) your mouse / keyboard / joystick / other input device (eg 3D mice, head trackers etc).
The kinds of things I wish to support:
Device-to-device mappings (eg mouse to keyboard)
Toggles (ie convert momentary to hold)
Button Spam (Your basic rapid fire etc)
Analog/Digital conversion (eg Keyboard <--> Joystick) mappings
Shift States (any input method as a shift button)
Game-Specific code (eg Extended versions of classes to try and track game states such as "Are we currently in chat mode or not?")

I see no major hurdles stopping me from implementing any of the above features - I have pretty much written all of them before, it is the UI that I see as the major hurdle.

What I am thinking is to make the UI fixed-width (Say 800px) but variable height.
Within the main window, there would be one or more "modules". Modules are variable height.
Each module performs a single task (eg remapping MButton to X) or a small set of related tasks (eg handles a POV hat).
A module is a class - there are various classes for the different kinds of mappings etc.
It would also need profile support - Users can save / load module setups and use one profile per game etc.

Modules can be extended by end-users to provide new functionality by placing a script containing an Extended class in a specified directory.

So on to the point of this post.
I am currently working on the proof-of-concept and would appreciate any input.
I have taken Lexikos' scrolling snippet, converted it to V2 and started creating the classes.
I can add modules, and have the scrollbars updating when you do so.

It is, however, pretty messy / cludgey still. Use of global vars, hard-wired class instance names etc.
Obviously, I would like to try to encapsulate everything within the classes, but this is where I am having problems.

Any help would be much appreciated. No part of the code is sacred, if there is a better way to achieve anything, I would like to hear it, even if it means a complete re-write.

I am quite happy to use UI libraries if absolutely necessary, but I am also mindful that the UI has not recieved a V2 overhaul yet (Though I see fincs is maybe starting a drive in that direction) so I guess I need to keep my options open there.
User avatar
evilC
Posts: 4824
Joined: 27 Feb 2014, 12:30

Re: [V2] Trying to make a class-based modular UI

10 Jun 2014, 17:18

Code: Select all

OnExit, MainGuiClose

Instance := New MainClass()
return

AddPressed:
   Gui, Main: Submit, NoHide
   Instance.AddModule({name: "" . Instance.ModuleCtr + 1, height: ModuleHeight})
   Gui, Main: +Resize +0x300000 ; WS_VSCROLL | WS_HSCROLL
   return

MainGuiSize:
   UpdateScrollBars(A_Gui, A_GuiWidth, A_GuiHeight, Instance.Modules)
   return

MainGuiClose:
   ExitApp
   return

Class MainClass {
   __New() {
      this.Modules := []
      this.ModuleCtr := 0
      this.ModuleRectY := 0

      global ModuleHeight

      OnMessage(0x115, "OnScroll") ; WM_VSCROLL
      OnMessage(0x114, "OnScroll") ; WM_HSCROLL

      Gui, Main: New, , Main GUI

      Gui, Main: Add, Text, x10 , Height
      Gui, Main: Add, Edit, xp+50 yp-2 vModuleHeight, 100
      Gui, Main: Add, Button, xp+50 yp-2 gAddPressed, Add

      ; Calculate height of static part of UI
      Gui, Main: +LastFound
      ControlList := WinGetControls()
      y := 0
      Loop ControlList.MaxIndex(){
         ControlGetPos(cX, cY, cW, cH, ControlList[A_Index])
         if (cH > y){
            y := cH
         }
      }
      y += 5
      this.StaticHeight := y

      Gui, Main: Show, W800 H200

      Gui, Main: +LastFound

      Gui, Main: +Resize +0x300000 ; WS_VSCROLL | WS_HSCROLL

      return this
   }

   AddModule(params){
      module := New this.NestedClass({parent: this, name: params.name, height: params.height})
      this.Modules.Insert(module)
      module.index := this.Modules.MaxIndex()
      module.Init()

      this.ModuleRectY := this.GetModulesHeight()
      this.ModuleCtr++

      UpdateScrollBars(A_Gui, A_GuiWidth, A_GuiHeight, this.Modules)

      ;module := ""   ; ToDo: do I need this?
   }

   GetModuleY(idx){
      y := this.StaticHeight + 5
      Loop this.Modules.MaxIndex(){
         if (idx == A_Index){
            ; module we want Y coord for
            return y
         } else {
            ; module above
            y += this.Modules[A_index].h + 10
         }
      }
      return 0
   }

   GetModulesHeight(){
      y := this.StaticHeight + 5
      Loop this.Modules.MaxIndex(){
         if (A_Index){
            y += 10
         }
         y += this.Modules[A_index].h
      }
      return y
   }

   IterateModules(){
      if (this.Modules){
         Loop this.Modules.MaxIndex(){
            this.Modules[A_Index].SayHi()
         }
      }
   }

   Class NestedClass {
      __New(params) {
         this.parent := params.parent
         this.name := params.name
         this.height := params.height

         return this
      }

      Init(){
         name := this.name
         y := this.parent.GetModuleY(this.index)

         Gui, %name%: New, +ParentMain -Border
         Gui, %name%: Add, Text,,Module: %name%
         Gui, %name%: Show, x5 y%y% W790 H%this.height%

         Gui, %name%: +LastFound
         this.hwnd := WinExist()
         GetClientSize(this.hwnd, w, h)
         this.w := w
         this.h := h
      }

      SayHi(){
         msgbox("Hi, I am " . this.name)
      }

   }

}

OnScroll(wParam, lParam, msg, hwnd)
{
    static SIF_ALL:=0x17, SCROLL_STEP:=10
    
    bar := msg=0x115 ; SB_HORZ=0, SB_VERT=1
    
    VarSetCapacity(si, 28, 0)
    NumPut(28, si) ; cbSize
    NumPut(SIF_ALL, si, 4) ; fMask
    if !DllCall("GetScrollInfo", "uint", hwnd, "int", bar, "uint", &si)
        return
    
    VarSetCapacity(rect, 16)
    DllCall("GetClientRect", "uint", hwnd, "uint", &rect)
    
    new_pos := NumGet(si, 20) ; nPos
    
    action := wParam & 0xFFFF
    if action = 0 ; SB_LINEUP
        new_pos -= SCROLL_STEP
    else if action = 1 ; SB_LINEDOWN
        new_pos += SCROLL_STEP
    else if action = 2 ; SB_PAGEUP
        new_pos -= NumGet(rect, 12, "int") - SCROLL_STEP
    else if action = 3 ; SB_PAGEDOWN
        new_pos += NumGet(rect, 12, "int") - SCROLL_STEP
    else if (action = 5 || action = 4) ; SB_THUMBTRACK || SB_THUMBPOSITION
        new_pos := wParam>>16
    else if action = 6 ; SB_TOP
        new_pos := NumGet(si, 8, "int") ; nMin
    else if action = 7 ; SB_BOTTOM
        new_pos := NumGet(si, 12, "int") ; nMax
    else
        return
    
    min := NumGet(si, 8, "int") ; nMin
    max := NumGet(si, 12, "int") - NumGet(si, 16) ; nMax-nPage
    new_pos := new_pos > max ? max : new_pos
    new_pos := new_pos < min ? min : new_pos
    
    old_pos := NumGet(si, 20, "int") ; nPos
    
    x := y := 0
    if bar = 0 ; SB_HORZ
        x := old_pos-new_pos
    else
        y := old_pos-new_pos
    ; Scroll contents of window and invalidate uncovered area.
    DllCall("ScrollWindow", "uint", hwnd, "int", x, "int", y, "uint", 0, "uint", 0)
    
    ; Update scroll bar.
    NumPut(new_pos, si, 20, "int") ; nPos
    DllCall("SetScrollInfo", "uint", hwnd, "int", bar, "uint", &si, "int", 1)
}

UpdateScrollBars(GuiNum, GuiWidth, GuiHeight, ModuleArray)
{
    static SIF_RANGE:=0x1, SIF_PAGE:=0x2, SIF_DISABLENOSCROLL:=0x8, SB_HORZ:=0, SB_VERT:=1
    global Instance

    ; Calculate scrolling area.
    Top := 0
    Left := 0
    Right := 800
    Bottom := Instance.ModuleRectY
    ScrollWidth := Right
    ScrollHeight := Bottom

    ;Gui, %GuiNum%:Default
    Gui, Main: +LastFound
    
    ; Initialize SCROLLINFO.
    VarSetCapacity(si, 28, 0)
    NumPut(28, si) ; cbSize
    NumPut(SIF_RANGE | SIF_PAGE, si, 4) ; fMask
    
    ; Update horizontal scroll bar.
    NumPut(ScrollWidth, si, 12) ; nMax
    NumPut(GuiWidth, si, 16) ; nPage
    DllCall("SetScrollInfo", "uint", WinExist(), "uint", SB_HORZ, "uint", &si, "int", 1)
    
    ; Update vertical scroll bar.
    ;     NumPut(SIF_RANGE | SIF_PAGE | SIF_DISABLENOSCROLL, si, 4) ; fMask
    NumPut(ScrollHeight, si, 12) ; nMax
    NumPut(GuiHeight, si, 16) ; nPage
    DllCall("SetScrollInfo", "uint", WinExist(), "uint", SB_VERT, "uint", &si, "int", 1)
    
    if (Left < 0 && Right < GuiWidth)
        x := Abs(Left) > GuiWidth-Right ? GuiWidth-Right : Abs(Left)
    if (Top < 0 && Bottom < GuiHeight)
        y := Abs(Top) > GuiHeight-Bottom ? GuiHeight-Bottom : Abs(Top)
    if (x || y)
        DllCall("ScrollWindow", "uint", WinExist(), "int", x, "int", y, "uint", 0, "uint", 0)
}

GetClientSize(hwnd, ByRef w, ByRef h)
{
    VarSetCapacity(rc, 16)
    DllCall("GetClientRect", "uint", hwnd, "uint", &rc)
    w := NumGet(rc, 8, "int")
    h := NumGet(rc, 12, "int")
}
User avatar
evilC
Posts: 4824
Joined: 27 Feb 2014, 12:30

Re: [V2] Trying to make a class-based modular UI

29 Jul 2014, 11:08

OK, so I made quite a bit of progress here - although I took a slightly different route than expected.
I went back to AHK v1 and extended Fincs' AFC to allow a scrollable sub-panel within the main window that can hold any number of child windows, tiling vertically.

Here is a video of it in action:
http://evilc.com/files/tmp/test2.wmv

The source is here:
https://github.com/evilC/UCR

It's still a little hacky, I am not very happy with the window layout code (Hard coded scroll bar sizes etc) but the goal was to make a proof-of-concept, and in that regard I think it goes far enough.

The project is currently on hold whilst I see what is going to happen regarding AHK V2 and Fincs' Proposed GUI Changes.

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: Ragnar and 28 guests