Non-rectangular hotspots

Posted by Twisol on Mon 22 Feb 2010 04:49 AM — 1 posts, 5,362 views.

USA #0
I was messing around a bit earlier, and I came up with a simple way to create non-rectangular hotspots using the pixel-sensitive hotspots recently added. Below is a rather cluttered example, very slightly based on my miniwidget framework. It creates a triangular hotspot over one half of the window, over which the cursor turns into a crosshair.

local point_in_polygon = function(x, y, points)
  assert(#points % 2 == 0, "Unfinished point pair")
  
  -- below loop (and just this loop) adapted from:
  -- http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
  local i, j, c = 1, #points-1, false
  while i < #points do
    local ix, iy = points[i], points[i+1]
    local jx, jy = points[j], points[j+1]
    
    local test = ((iy > y) ~= (jy > y)) and
     (x < (jx - ix) * (y - iy) / (jy - iy) + ix)
    
    if test then
      c = not c
    end
    
    j, i = i, i + 2
  end
  
  -- If it's not fully within the polygon, check the sides.
  -- Compare the slope of each side against the
  -- slope of the point and a vertice.
  if not c then
    i, j = 1, #points-1
    while i < #points do
      local ix, iy = points[i], points[i+1]
      local jx, jy = points[j], points[j+1]
      
      local line_slope =
       (ix ~= jx) and (jy - iy) / (jx - ix) or nil
      local test_slope =
       (ix ~= x) and (y - iy) / (x - ix) or nil
      
      -- If the slopes match, also check that the point
      -- lies on the line segment itself.
      -- Only check one axis, since the slope will guarantee
      -- the other.
      if line_slope == test_slope and
       x <= math.max(ix, jx) and x >= math.min(ix, jx) then
        c = true
        break
      end
      
      j, i = i, i + 2
    end
  end
  
  return c
end

local windows = {[0] = 0}

local mouse_event = function(event)
  return function(flags, id)
    local w = windows[id]
    
    local x, y = WindowInfo(id, 14), WindowInfo(id, 15)
    for _,hotspot in ipairs(w.children) do
      if point_in_polygon(x, y, hotspot.points) then
        local func = hotspot.event_handlers[event]
        if func then func() end
        break
      end
    end
  end
end

HotspotCallbacks = {
  mousedown = mouse_event("mousedown"),
  cancelmousedown = mouse_event("cancelmousedown"),
  mouseup = mouse_event("mouseup"),
  mouseover = mouse_event("mouseover"),
  cancelmouseover = mouse_event("cancelmouseover"),
}

Window = {
  new = function(width, height)
    local w = {}
    local name = GetPluginID() .. "_" .. (windows[0]+1)
    
    w.name = name
    w.width = width
    w.height = height
    w.children = {}
    w.event_handlers = {}
    
    WindowCreate(name, 0, 0, width, height, 0, 2, ColourNameToRGB("white"))
    WindowShow(name, true)
    WindowAddHotspot(name, name, 0, 0, width, height,
     "HotspotCallbacks.mouseover",
     "HotspotCallbacks.cancelmouseover",
     "HotspotCallbacks.mousedown",
     "HotspotCallbacks.cancelmousedown",
     "HotspotCallbacks.mouseup",
     "", 0, 1)
    
    windows[name] = w
    windows[0] = windows[0] + 1
    
    return w
  end,
}

w = Window.new(50, 50)

table.insert(w.children, {
   points = {
     0,0,
     50,0,
     50,50,
   },
   
   event_handlers = {
     mouseover = function()
       SetCursor(3)
     end,
     cancelmouseover = function()
       SetCursor(0)
     end,
   },
 }
)


If you were to abstract it away a bit further, it's easy to see how one could have hotspots based on distance from a central point (circle) or other more complex hotspots.