[Home] [Downloads] [Search] [Help/forum]

Gammon Forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Miniwindows
. . -> [Subject]  Widget framework
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Widget framework

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page


Pages: 1 2  

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Thu 19 Nov 2009 08:38 PM (UTC)

Amended on Thu 19 Nov 2009 08:39 PM (UTC) by Twisol

Message
Today I hacked together a little code to try out an idea I had. It's not much at this point, just two types (one inherited from the other), but with some work I think it would become an easy way to create and use miniwindows, as well as derive your own custom types of miniwindows. This is what I have so far:


Window = {
  new = function(name)
    local o = setmetatable({}, Window)
    
    o.name = name
    o.x = 0
    o.y = 0
    o.width = 1
    o.height = 1
    
    -- Dummy window.
    WindowCreate(o.name, 0, 0, 1, 1, 0, 0, 0)

    return o
  end,
  
  __index = {
    Clear = function(self, color)
      WindowRectOp(self.name, 2, 0, 0, 0, 0, color or 0x000000)
    end,
    
    Show = function(self, bShow)
      WindowShow(self.name, bShow)
    end,

    SelectFont = function(self, fontname, fontsize)
      local fid = nil
      local fontlist = WindowFontList(self.name)

      if fontlist then
        for _,v in ipairs(fontlist) do
          if WindowFontInfo(self.name, v, 21) == fontname and
             WindowFontInfo(self.name, v, 8) == fontsize then
            break
          end
        end
        
        if not fid then
          fid = self.name .. "-f" .. #fontlist+1
          WindowFont(self.name, fid, fontname, fontsize, false, false, false, false, 1, 0)
        end
      else
        fid = self.name .. "-f1"
        WindowFont(self.name, fid, fontname, fontsize, false, false, false, false, 1, 0)
      end
      
      return fid      
    end,
  },
}
setmetatable(Window, {__index = Window.__index})

CharacterGrid = {
  new = function(name, width, height, font, px)
    local o = Window.new(name)
    setmetatable(o, CharacterGrid)
    
    o.mapwidth = width
    o.mapheight = height
    
    o.font = {
      name = font or "fixedsys",
      id = nil,
      size = px or 10,
      width = 1,
      height = 1,
    }
    o.grid = {
    }
    
    o.font.id = o:SelectFont(o.font.name, o.font.size)
    
    return o
  end,
  
  -- "Instance" methods
  __index = {
    DrawCell = function(self, cell, x, y)
      -- Index into the appropriate cell, and add for the window edge
      local x = self.font.width*x + 1
      local y = self.font.height*y + 1
      
      WindowText(self.name, self.font.id, cell.char, x, y, 0, 0, cell.style, false)
    end,
        
    DrawRow = function(self, line, row)
      for i = 1, math.min(table.getn(line), self.mapwidth) do
        self:DrawCell(line[i], i-1, row)
      end
    end,
    
    DrawGrid = function(self)
      self.font.id = self:SelectFont(self.font.name, self.font.size)
      
      self.font.width = WindowTextWidth(self.name, self.font.id, "#")
      self.font.height = WindowFontInfo(self.name, self.font.id,  1)
      
      self.width = self.font.width * self.mapwidth;
      self.height = self.font.height * self.mapheight;
      
      WindowCreate(self.name, self.x, self.y, self.width, self.height, 6, 2, 0x000000)
      WindowPosition(self.name, self.x, self.y, 6, 2)
      
      for i = 1, math.min(table.getn(self.grid), self.mapheight) do
        self:DrawRow(self.grid[i], i-1)
      end
    end,
    
    Draw = function(self)
      self:Clear(0x000000)
      self:DrawGrid()
    end,
  },
}
setmetatable(CharacterGrid.__index, Window)
setmetatable(CharacterGrid, {__index = CharacterGrid.__index})


This code defines two types, one a generic Window and the other a derived CharacterGrid. The CharacterGrid itself is adapted directly from a table I use in my MapWindow plugin. It's quite easy to use, as it hides the complex details away. The grid table itself is table of lines, which are tables of cells, which store 'char' and 'style' (color) information.

The implementations of the two widget types do leave something to be desired at the moment, but the interface that you use to manipulate the grid is simple.


grid = CharacterGrid.new("TestGrid", 5, 5)
grid.x = 10
grid.y = 10

for i = 1, 5 do
  local line = {}
  for j = 1, 5 do
    line[j] = {char = "X", style = 0xFF0000}
  end
  grid.grid[i] = line
end

grid:Draw()
grid:Show(true)


Running this code produces a stamp-like 5x5 grid of blue X's. (I admit I was slightly surprised by this; I expected red. But I digress) Any thoughts and/or contributions would be appreciated, because while I'd love to see a set of MUSHclient window widgets, it's not something I'd care to undertake on my own.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Nick Gammon   Australia  (21,322 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Thu 19 Nov 2009 08:54 PM (UTC)
Message
Re the colour:

It's an endian thing. If you look up red in the colour picker, you see:


MXP: #FF0000
JScript: 0x0000FF


The HTML/MXP system puts the red byte first, where you expect it (red/green/blue). But the actual computer word, which has has the least significant byte "on the left" so to speak, shows it as the low-order byte.

Template:post=7918 Please see the forum thread: http://gammon.com.au/forum/?id=7918.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #2 on Thu 19 Nov 2009 08:56 PM (UTC)

Amended on Thu 19 Nov 2009 08:57 PM (UTC) by Twisol

Message
Nick Gammon said:

Re the colour:

It's an endian thing. If you look up red in the colour picker, you see:


MXP: #FF0000
JScript: 0x0000FF


The HTML/MXP system puts the red byte first, where you expect it (red/green/blue). But the actual computer word, which has has the least significant byte "on the left" so to speak, shows it as the low-order byte.

(post=7918)


Yes, I recall encountering that once before. It just surprised me, is all. (I'm used to HTML/CSS and the #RRGGBB style)

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #3 on Thu 19 Nov 2009 10:06 PM (UTC)
Message
Shameless plug:

Template:post=9097 Please see the forum thread: http://gammon.com.au/forum/?id=9097.
[Go to top] top

Posted by Blainer   (191 posts)  [Biography] bio
Date Reply #4 on Fri 20 Nov 2009 01:56 AM (UTC)
Message
Don't you hate when you write 3000 lines of code then find out there's a better way...
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #5 on Fri 20 Nov 2009 03:40 AM (UTC)
Message
@WillFa: It would be great to integrate those components into a Widget framework and make them reusable among other widgets. Would you mind if I tried adapting your code to the barebones demonstration I posted above (and perhaps took a few of your ideas)?

@Blainer: Yes. I went through that with a C++ class I called cConsole, when later I realized I could emulate nearly all of its output functionality with two custom stream manipulators (one for position, the other for color) which only took maybe ten lines of actual code...

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #6 on Fri 20 Nov 2009 05:03 AM (UTC)

Amended on Fri 20 Nov 2009 05:12 AM (UTC) by WillFa

Message
Twisol, Have at it. :)


The thing is that Infobox already does most things with miniwindows (I haven't added dragging, and there's no provisions for drawing bitmaps/pngs).

Your grid widget/mapmover can be implemented with InfoBox by just futzing with a couple of values...

In your Script file:

require "InfoBox"
MW = InfoBox:New("Test")
MW.Bar.barStyle = 0 -- no frame or gauge
MW:Font("Dina", 9) -- monospaced font
MW.padding = 0 -- smoosh lines together

local LookupColors = {}
for k,v in pairs(MW.ansiColors) do
  LookupColors[v] = k
end
for k,v in pairs(MW.customColourCodes) do
  LookupColors[v]=k
end


In your MapStart trigger:

MW.Bars={}


In your MapDataLine trigger:

local Caption = ""
for k,v in ipairs(TriggerStyleRuns) do
  if not LookupColors[v.textcolour] then
    -- Code to append to LookupColors if not found
  end
  Caption = string.format("%s@%s%s", Caption, LookupColors[v.textcolour], v.text)
end
MW:AddBar(Caption)


In your MapFinish trigger:

MW:Update()



The module really does more than just gauges, though it's original intention was to move the InfoBar function calls into a miniwindow
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #7 on Sun 22 Nov 2009 12:31 AM (UTC)
Message
I've opened a git repository [1] for the widget framework, in case anyone wants to collaborate. Big thanks to Will for his input so far, also!


[1]: http://github.com/Twisol/MUSHclient-MWidget

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #8 on Tue 24 Nov 2009 09:54 PM (UTC)
Message
Here is a demo plugin that lets you play TicTacToe in a miniwindow. My implementation of the game itself is undoubtedly rather bad (note the CheckWin/CheckRow functions), but the purpose of the plugin is just to showcase the CharacterGrid widget.


<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="TicTacToe"
   author="Soludra"
   id="03829336f30e9f2dcace86e9"
   language="Lua"
   purpose="Play a game of Tic Tac Toe!"
   date_written="2009-11-22 07:46:00"
   requires="4.43"
   version="1.0"
/>

<!--  Script  -->

<script>
<![CDATA[

CharacterGrid = require("MWidget.CharacterGrid")

-- Defines the default grid font.
TICTACTOE_FONT = "Lucida Console"

players = {
  [1] = {
    piece = 'X',
    color = 0x00FF00,
    lastmove = nil,
  },
  [2] = {
    piece = 'O',
    color = 0x0000FF,
    lastmove = nil,
  },
}
current_player = 1

-- cell we're hovering over now
hovered = nil

-- has the game been finished?
done = false

CheckRow = function(cell1, cell2, cell3)
  if cell1.char ~= ' ' and
     cell1.char == cell2.char and
     cell2.char == cell3.char then
    cell1.forecolor, cell2.forecolor, cell3.forecolor = 0xFF0000, 0xFF0000, 0xFF0000
    return true
  else
    return false
  end
end

CheckWin = function()
  done = CheckRow(grid:Cell(1, 1), grid:Cell(1, 2), grid:Cell(1, 3)) or
         CheckRow(grid:Cell(2, 1), grid:Cell(2, 2), grid:Cell(2, 3)) or
         CheckRow(grid:Cell(3, 1), grid:Cell(3, 2), grid:Cell(3, 3)) or
         CheckRow(grid:Cell(1, 1), grid:Cell(2, 1), grid:Cell(3, 1)) or
         CheckRow(grid:Cell(1, 2), grid:Cell(2, 2), grid:Cell(3, 2)) or
         CheckRow(grid:Cell(1, 3), grid:Cell(2, 3), grid:Cell(3, 3)) or
         CheckRow(grid:Cell(1, 1), grid:Cell(2, 2), grid:Cell(3, 3)) or
         CheckRow(grid:Cell(3, 1), grid:Cell(2, 2), grid:Cell(1, 3))
  return done
end

CellClick = function(flags, id)
  if done then
    return
  end
  
  local cell = grid:HotspotToCell(id)
  
  if cell.char ~= " " then
    return
  end
  
  local player = players[current_player]
  if player.lastmove then
    player.lastmove.backcolor = 0xFFFFFF
  end
  
  cell.char = player.piece
  cell.backcolor = player.color
  
  CheckWin()
  
  player.lastmove = cell
  current_player = (current_player%2) + 1
  grid:Draw()
end

CellFocus = function(flags, id)
  if done then
    return
  end
  
  local cell = grid:HotspotToCell(id)
  
  if cell.char ~= " " then
    return
  end
  
  cell.forecolor = players[current_player].color
  cell.char = players[current_player].piece
  
  hovered = id
  grid:Draw()
end

CellBlur = function(flags, id)
  if done then
    return
  end
  
  if id ~= hovered then
    return
  end
  local cell = grid:HotspotToCell(hovered)
  
  cell.forecolor = 0x000000
  cell.char = ' '
  
  hovered = nil
  grid:Draw()
end

Reset = function()
  current_player = 1
  done = false
  
  grid:ResetGrid()
  
  for y = 1, 3 do
    for x = 1, 3 do
      local cell = grid:Cell(x, y)
      cell.forecolor = 0x000000
      cell.hotspot = {
        mouseup = "CellClick",
        mouseover = "CellFocus",
        cancelmouseover = "CellBlur",
      }
    end
  end
  
  grid:Draw()
end

OnPluginInstall = function()
  grid = CharacterGrid.new(3, 3)
  grid:Anchor(12)
  grid:Font(TICTACTOE_FONT, 15)
  grid.backcolor = 0xFFFFFF

  Reset()
  grid:Show()
end

OnPluginClose = function()
  grid:Destroy()
end

OnPluginEnable = OnPluginInstall
OnPluginDisable = OnPluginClose

]]>
</script>
</muclient>


The grid appears by default in the center of the screen. Hovering over any available space will display the current player's color/piece. It highlights each player's last move, and highlights a winning line in blue (and also locks the grid).

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #9 on Tue 01 Dec 2009 08:56 AM (UTC)

Amended on Tue 01 Dec 2009 08:57 AM (UTC) by Twisol

Message
I've just committed a version that implements a very small subset of WillFa's InfoBox functionality. It's barely even begun, in my opinion, but some of the other functionality in InfoBox (like the gauges being collected together under a common frame) are likely going to be implemented as separate widgets altogether.

Not to be conceited, but I particularly like my implementation of the gauge effects. I think it makes it rather easy to define new (and potentially complex) gauge effects, and it simplifies the actual drawing code. I left off implementing fixed gradients because it was somewhat difficult for me to transpose the variables/logic used in InfoBox to my model, but I did create a substitute 'meter' effect, which simply places a line at the point of value.

EDIT: Latest source can always be found at my GitHub respository; see [1].


[1] http://github.com/Twisol/MUSHclient-MWidget

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by David Haley   USA  (3,881 posts)  [Biography] bio
Date Reply #10 on Tue 01 Dec 2009 07:23 PM (UTC)
Message
I think that this is really quite nifty -- I look forward to playing around with it more when I get the chance. Having a solid framework for common widgets will make developing "sub-interfaces" much more efficient.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #11 on Tue 01 Dec 2009 07:24 PM (UTC)
Message
Thanks! I'm glad you like the idea. ^_^

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Cage_fire_2000   USA  (119 posts)  [Biography] bio
Date Reply #12 on Tue 01 Dec 2009 09:28 PM (UTC)

Amended on Tue 01 Dec 2009 09:38 PM (UTC) by Cage_fire_2000

Message
Dang, I was working on something like this, thinking that an object oriented interface would be easier, but coding it in the first place is hard, and there's lots of stuff I don't really know. I was thinking it could make it so I could setup labels and buttons really easy, you know standard controls, maybe a listbox, stuff like that, but the problem is changing what's displayed, if you don't have a background drawn underneath, or you want to move the control somewhere, you need to clear the entire window and redraw everything, so you have to record everything to draw, and what order to draw it. Frankly, making a module for everything would probably be like 1000 times harder than any code you'd actually need to make with it(at least what I've done so far), and still might not do everything you'd want it to. I suppose a basic one wouldn't be two hard, where you simply put all the miniwindow functions in a general class, I already started something like that although I found it hard to continue working on it. It also has the problem of eating up memory making objects for everything. I find myself wondering whether it's worth it to make. Then again I do get bored easily when I have to figure out the mechanics of everything myself.

Edit: Hmm, to be honest it's the drawing order I was having a hard time figuring out, I suppose the rest of it you could have a table of controls on the form, with a field for the type of control, any miscellaneous data, etc. Hmm, I suppose... if it's an array, then the order of the array could be the z-order, but then I'd need another table to lookup controls by name... It all gives me a headache which is why I haven't gotten very far.

Edit: Not to mention you need to have it check whether the control or whatever still exists or not. God, I need to stop thinking about it before my head explodes.
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #13 on Tue 01 Dec 2009 09:39 PM (UTC)
Message
Since I've already mostly set up the object/class system and organization, feel free to create your own widgets from what I have so far. If they're general-purpose enough (like a listbox), I'd be glad to add them to the framework. Check out CharacterGrid and Gauge for examples of how to derive a widget.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

Posted by Twisol   USA  (2,257 posts)  [Biography] bio
Date Reply #14 on Thu 10 Dec 2009 11:03 PM (UTC)

Amended on Thu 10 Dec 2009 11:06 PM (UTC) by Twisol

Message
I've wrapped most/all of the Window*() functions into the Window base type, acting as a layer between the widget code and MUSHclient. I aimed for simplicity and intuitiveness where I could; for example, I split the WindowCircleOp() and WindowImageOp() functions into multiple methods, each corresponding to a separate value of the 'action' parameter.

After a long while deliberating my options, I felt that wrapping the Window*() functionality this way would be the easiest way to implement window "frames" similar to those surrounding windows in Windows. For simplicity, I wanted (0,0) to correspond to the upper-left edge of the "inside" of the frame, rather than the upper-left of the frame itself. This is a similar setup to the Win32 behavior anyways, though it will still be entirely possible to draw on the borders by supplying negative coordinates.

The only part I'm not really happy with is WindowRectOp; I'm not really sure how to break that down. WindowCircleOp allows for the drawing of rectangles, covering RectOp's actions 1 and 2, and I separately created an InvertRectangle() method to cover action 3. The rest I'm not sure about.


While there certainly aren't many widgets available yet, the Window layer alone might be fun to play with. I'd appreciate it if anyone else could test it out and let me know what their feelings are.

'Soludra' on Achaea

Blog: http://jonathan.com/
GitHub: http://github.com/Twisol
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


18,403 views.

This is page 1, subject is 2 pages long: 1 2  [Next page]

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at FutureQuest]