From 41b4072c2ab1134b7ebcb7ec86a7e7012af953a7 Mon Sep 17 00:00:00 2001 From: Ian C Date: Mon, 17 Oct 2005 00:00:52 +0000 Subject: Added TTextList, TScrollbar and a simple file selector. Change TFloatNumber to TDoubleNumber. --- simplegui.mod/simplegui.bmx | 670 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 639 insertions(+), 31 deletions(-) diff --git a/simplegui.mod/simplegui.bmx b/simplegui.mod/simplegui.bmx index 79ba7eb..a6dab25 100644 --- a/simplegui.mod/simplegui.bmx +++ b/simplegui.mod/simplegui.bmx @@ -11,8 +11,9 @@ ModuleInfo "Version: $Revision$" ' $Id$ Strict -Import brl.Max2D -Import brl.Basic +Import brl.max2d +Import brl.filesystem +Import brl.basic Import noddybox.bitmapfont Rem @@ -31,7 +32,24 @@ EndRem Type TWidget Abstract Rem - bbdoc: If true the widget is displayed and can be used. + bbdoc: This widget consumes click events + EndRem + Const HAS_CLICK:Int=1 + Rem + bbdoc: This widget consumes drag events + EndRem + Const HAS_DRAG:Int=2 + Rem + bbdoc: This widget consumes key events + EndRem + Const HAS_KEY:Int=4 + + Rem + bbdoc: If true the widget is displayed. + EndRem + Field displayed:Int + Rem + bbdoc: If true the widget can be interacted with. EndRem Field enabled:Int Rem @@ -67,7 +85,7 @@ Type TWidget Abstract EndRem Field owner:TGUIHandler Rem - bbdoc: True if this widget consumes events, False otherwise. + bbdoc: The mask of consumed events, zero otherwise. EndRem Field consumes:Int @@ -81,7 +99,9 @@ Type TWidget Abstract bbdoc: Override this for when the mouse enters. Call this parent version to handle @mouse_over too. EndRem Method MouseEnter() - mouse_over=True + If displayed And enabled + mouse_over=True + EndIf End Method Rem @@ -97,6 +117,13 @@ Type TWidget Abstract Method HandleClick() End Method + Rem + bbdoc: Handles mouse dragging. + about: @mx and @my are the changes in mouse co-ordinates since the last drag. + EndRem + Method HandleDrag(mx:Int, my:Int) + End Method + Rem bbdoc: Must be provided by a widget to draw itself. EndRem @@ -158,8 +185,9 @@ Type TPanel Extends TWidget EndRem Function Create:TPanel(gui:TGUIHandler,x:Int, y:Int, w:Int, h:Int) Local o:TPanel=New TPanel + o.displayed=True o.enabled=True - o.consumes=False + o.consumes=0 If x=-1 x=(GraphicsWidth()-w)/2 @@ -197,8 +225,9 @@ Type TLabel Extends TWidget EndRem Function Create:TLabel(gui:TGUIHandler,x:Int, y:Int, text:String) Local o:TLabel=New TLabel + o.displayed=True o.enabled=True - o.consumes=False + o.consumes=0 o.x=x o.y=y o.w=TGUIFont.font.TextWidth(text)+2 @@ -241,6 +270,16 @@ Type TText Extends TWidget EndRem Const POSITIVE:Int=4 + Rem + bbdoc: The text box handles a basic set of filename characters (alphanumerics, periods, spaces and underscores) + EndRem + Const FILENAME:Int=8 + + Rem + bbdoc: The text box is readonly + EndRem + Const READONLY:Int=256 + Rem bbdoc: Create a TText widget. @@ -251,8 +290,9 @@ Type TText Extends TWidget EndRem Function Create:TText(gui:TGUIHandler,x:Int, y:Int, text:String, maxlen:Int, mode:Int=0, callback(w:TWidget)=Null) Local o:TText=New TText + o.displayed=True o.enabled=True - o.consumes=True + o.consumes=HAS_CLICK|HAS_KEY o.x=x o.y=y o.maxlen=maxlen @@ -287,10 +327,16 @@ Type TText Extends TWidget End Method Method HandleClick() - owner.SetFocus(Self) + If Not(mode & READONLY) + owner.SetFocus(Self) + EndIf End Method Method HandleKey(k:Int) + If (mode & READONLY) + Return + EndIf + If k=27 text="" Else If k=8 @@ -316,7 +362,13 @@ Type TText Extends TWidget text:+Chr(k) EndIf Else - text:+Chr(k) + If (mode & FILENAME) + If (k>=Asc("a") And k<=Asc("z")) Or (k>=Asc("A") And k<=Asc("Z")) Or (k>=Asc("0") And k<=Asc("9")) Or k=Asc(" ") Or k=Asc(".") Or k=Asc("_") + text:+Chr(k) + EndIf + Else + text:+Chr(k) + EndIf EndIf EndIf End Method @@ -324,7 +376,7 @@ Type TText Extends TWidget Method Draw() Local s:String=text - If owner.GetFocus()=Self + If owner.GetFocus()=Self And Not (mode & READONLY) SetColor(128,128,128) s:+"_" Else @@ -355,8 +407,9 @@ Type TCheckbox Extends TWidget EndRem Function Create:TCheckbox(gui:TGUIHandler, x:Int, y:Int, text:String, callback(w:TWidget)=Null) Local o:TCheckbox=New TCheckbox + o.displayed=True o.enabled=True - o.consumes=True + o.consumes=HAS_CLICK o.x=x o.y=y o.w=TGUIFont.font.TextWidth(" "+text)+4+TGUIFont.font.MaxHeight() @@ -409,8 +462,9 @@ Type TButton Extends TWidget EndRem Function Create:TButton(gui:TGUIHandler, x:Int, y:Int, w:Int, h:Int, text:String, callback(w:TWidget)) Local o:TButton=New TButton + o.displayed=True o.enabled=True - o.consumes=True + o.consumes=HAS_CLICK o.x=x o.y=y o.w=w @@ -462,8 +516,9 @@ Type TButtonList Extends TWidget Function Create:TButtonList(gui:TGUIHandler, x:Int, y:Int, options:String[], selected:Int, callback(w:TWidget)) Local o:TButtonList=New TButtonList Local maxw:Int=0 + o.displayed=True o.enabled=True - o.consumes=True + o.consumes=HAS_CLICK o.x=x o.y=y o.options=options @@ -500,7 +555,7 @@ Type TButtonList Extends TWidget SetColor(64,64,64) EndIf - DrawRect(x,y,w,h) + DrawRect(x,y,w,h) Draw3DBox(x+2,y+2,h-4,h-4,False,1) TGUIFont.font.Draw(text,x+TGUIFont.font.MaxHeight()+3,y+1) @@ -508,29 +563,224 @@ Type TButtonList Extends TWidget End Type Rem -bbdoc: A number up/down widget for floats +bbdoc: Optional type to use for TTextList +about: If the objects stored as TTextList options are derived from this type then tabular lists can be created. +EndRem +Type TTextListItem Abstract + Rem + about: The number of columns. + returns: The number of columns + EndRem + Method Columns:Int() Abstract + + Rem + about: A column offset. + returns: The column offset for column @i (numbered from zero). + EndRem + Method ColumnOffset:Int(i:Int) Abstract + + Rem + about: Column data. + returns: The column data for column @i (numbered from zero). + EndRem + Method ColumnData:String(i:Int) Abstract +End Type + +Rem +bbdoc: A widget that displays text items as a list. EndRem -Type TNumberFloat Extends TWidget +Type TTextList Extends TWidget + + Field options:Object[] + Field selected:Int + Field num:Int + Field top:Int + Field rows:Int + + Rem + bbdoc: Create a TTextList widget. + returns: The created widget. + about: @gui is the @TGUIHandler object managing this wiget. @x, @y, and @w are its position and width. + about: @rows are the number of items to display at a time. + about: @callback is called when the selection changes. + EndRem + Function Create:TTextList(gui:TGUIHandler, x:Int, y:Int, w:Int, rows:Int, callback(w:TWidget)=Null) + Local o:TTextList=New TTextList + Local maxw:Int=0 + o.displayed=True + o.enabled=True + o.consumes=HAS_CLICK + o.x=x + o.y=y + o.options=Null + o.selected=-1 + o.num=0 + o.top=0 + o.rows=rows + + o.w=w + o.h=TGUIFont.font.MaxHeight()*rows + o.callback=callback + gui.Register(o) + Return o + End Function + + Rem + bbdoc: Sets the options to display in the list. + about: @opts is an array of objects. Note: If the objects passed are derived from TTextListItem then tabular output is generated. + about: If they are not derived, ToString() is called on the objects to display them, so you are not restricted to the type of object that + about: can be passed. + EndRem + Method SetOptions(opts:Object[]) + options=opts + num=opts.length + selected=-1 + top=0 + End Method + + Rem + bbdoc: Gets the selected item's index. + returns: The selected item's index, or -1 if nothing is selected. + EndRem + Method GetSelectedIndex:Int() + Return selected + End Method + + Rem + bbdoc: Gets the selected item. + returns: The selected item, or null if nothing is selected. + EndRem + Method GetSelectedItem:Object() + If options<>Null And selected<>-1 + Return options[selected] + Else + Return Null + EndIf + End Method + + Rem + bbdoc: Set the top row. + about: @i is the index of the item to display in the first row of the widget. + EndRem + Method SetTopRow(i:Int) + If options<>Null And iNull + Return top + Else + Return -1 + EndIf + End Method + + Rem + bbdoc: Ensure an item is visible. + about: @i is the index of the item that is to be made visible. + EndRem + Method EnsureVisible(i:Int) + If options<>Null And i=top+rows + top=Max(0,i-rows+1) + EndIf + EndIf + End Method + + Method HandleClick() + If options<>Null + Local sel:Int=top+(MouseY()-y)/TGUIFont.font.MaxHeight() + + If selNull + callback(Self) + EndIf + EndIf + EndIf + End Method + + Method Draw() + SetColor(64,64,64) + DrawRect(x,y,w,h) + + If options<>Null + Local vx:Int + Local vy:Int + Local vw:Int + Local vh:Int + + GetViewport(vx,vy,vw,vh) + + Local ty:Int=y + For Local f:Int=top Until top+rows + SetViewport(x,ty,w,TGUIFont.font.MaxHeight()) + + If f=selected + If mouse_over And MouseY()>=ty And MouseY()=ty And MouseY()Null + For Local f:Int=0 Until li.Columns() + TGUIFont.font.Draw(li.ColumnData(f),x+li.ColumnOffset(f),ty) + Next + Else + TGUIFont.font.Draw(options[f].ToString(),x,ty) + EndIf + EndIf + + ty:+TGUIFont.font.MaxHeight() + Next + + SetViewport(vx,vy,vw,vh) + EndIf + End Method +End Type + +Rem +bbdoc: A number up/down widget for doubles +EndRem +Type TNumberDouble Extends TWidget Rem bbdoc: The value EndRem - Field value:Float + Field value:Double Rem bbdoc: The increment/decrement EndRem - Field change:Float + Field change:Double Rem bbdoc: The maximum value EndRem - Field maxval:Float + Field maxval:Double Rem bbdoc: The minimum value EndRem - Field minval:Float + Field minval:Double Rem bbdoc: Create a TNumber widget. @@ -538,10 +788,11 @@ Type TNumberFloat Extends TWidget about: @gui is the @TGUIHandler object managing this wiget. @x and @y are its position. about: @callback is called when the value changes. EndRem - Function Create:TNumberFloat(gui:TGUIHandler, x:Int, y:Int, callback(w:TWidget)=Null) - Local o:TNumberFloat=New TNumberFloat + Function Create:TNumberDouble(gui:TGUIHandler, x:Int, y:Int, callback(w:TWidget)=Null) + Local o:TNumberDouble=New TNumberDouble + o.displayed=True o.enabled=True - o.consumes=True + o.consumes=HAS_CLICK o.value=0 o.minval=0 o.maxval=100 @@ -556,7 +807,7 @@ Type TNumberFloat Extends TWidget End Function Method HandleClick() - Local old:Float=value + Local old:Double=value If MouseX()h) + o.SetBar(0,100,10) + o.callback=callback + gui.Register(o) + Return o + End Function + + Rem + bbdoc: Set parameters for scroll bar + about: @min_val and @max_val are the values at the top and bottom of the scroll bar. @page_size is the numbers between the two that + about: is covered by the block in the scrollbar. Note: The maximum value of @value is @max_val minus @page_size. + EndRem + Method SetBar(min_val:Double, max_val:Double, page_size:Double) + minval=min_val + maxval=max_val + page=page_size + + If horiz + stepsize=(maxval-minval)/w + blocksize=Min(w,Max(1,page/stepsize)) + bc=x + Else + stepsize=(maxval-minval)/h + blocksize=Min(h,Max(1,page/stepsize)) + bc=y + EndIf + End Method + + Rem + bbdoc: Get the current value of the scrollbar. + returns: The current position of the scrollbar inbetween the minimum and maximum values. + EndRem + Method GetValue:Double() + If horiz + Return minval+(bc-x)*stepsize + Else + Return minval+(bc-y)*stepsize + EndIf + End Method + + Rem + bbdoc: Sets the current value of the scrollbar. + EndRem + Method SetValue(val:Double) + If horiz + bc=Max(x,Min(x+w-blocksize,x+(val-minval)/stepsize)) + Else + bc=Max(y,Min(y+h-blocksize,y+(val-minval)/stepsize)) + EndIf + End Method + + Method HandleClick() + Local old:Double=GetValue() + + If horiz + bc=Max(x,Min(x+w-blocksize,MouseX())) + Else + bc=Max(y,Min(y+h-blocksize,MouseY())) + EndIf + + If GetValue()<>old And callback<>Null + callback(Self) + EndIf + End Method + + Method HandleDrag(mx:Int, my:Int) + Local old:Double=GetValue() + + If horiz + bc=Max(x,Min(x+w-blocksize,bc+mx)) + Else + bc=Max(y,Min(y+h-blocksize,bc+my)) + EndIf + + If GetValue()<>old And callback<>Null + callback(Self) + EndIf + End Method + + Method Draw() + SetColor(64,64,64) + DrawRect(x,y,w,h) + If horiz + Draw3DBox(bc,y,blocksize,h,False,1) + Else + Draw3DBox(x,bc,w,blocksize,False,1) + EndIf + End Method +End Type + Rem bbdoc: Handles the GUI. EndRem @@ -692,6 +1062,10 @@ Type TGUIHandler Field m_focus:TWidget Field m_over:TWidget Field m_clicked:TWidget + Field m_dragging:Int + Field m_dragx:Int + Field m_dragy:Int + Field m_dragobj:TWidget Rem bbdoc: Create a GUI Handler. @@ -706,13 +1080,17 @@ Type TGUIHandler o.m_focus=Null o.m_over=Null o.m_clicked=Null + o.m_dragging=False + o.m_dragx=0 + o.m_dragy=0 + o.m_dragobj=Null Return o End Function Rem bbdoc: Register a widget. - about: Widgets call this themselves. + about: Widgets in the library call this themselves when creating. EndRem Method Register(w:TWidget) m_widgets.AddLast(w) @@ -736,6 +1114,16 @@ Type TGUIHandler Next End Method + Rem + bbdoc: Sets the displayed state of all widgets. + about: If @state is true then all widgets are enabled, otherwise all widgets are disabled + EndRem + Method SetDisplay(state:Int) + For Local w:TWidget=EachIn m_widgets + w.displayed=state + Next + End Method + Rem bbdoc: Sets the keyboard focus to the supplied widget. Pass null for no focus. EndRem @@ -761,6 +1149,14 @@ Type TGUIHandler Return last End Method + Rem + bbdoc: Whether a widget is dragging + returns: True if a widget is consuming a mouse drag. + EndRem + Method IsDragging:Int() + Return m_dragging + End Method + Rem bbdoc: Perform the event loop and pass any necessary events. about: KeyHit(KEY_MOUSELEFT) and GetChar() are used to consume events for the interface. @@ -769,12 +1165,19 @@ Type TGUIHandler Local x:Int=MouseX() Local y:Int=MouseY() Local b:Int=KeyHit(KEY_MOUSELEFT)>0 + Local drag:Int=KeyDown(KEY_MOUSELEFT) Local w:TWidget=LocateWidget(x,y) For Local wid:TWidget=EachIn m_widgets - If wid.enabled - wid.Draw() + If wid.displayed + If wid.enabled + wid.Draw() + Else + SetAlpha(0.3) + wid.Draw() + SetAlpha(1) + EndIf EndIf Next @@ -788,16 +1191,34 @@ Type TGUIHandler EndIf EndIf - If w<>Null And w.enabled And b + If w<>Null And w.enabled And w.consumes&TWidget.HAS_CLICK=TWidget.HAS_CLICK And b And Not m_dragging w.HandleClick() m_clicked=w EndIf Local k:Int=GetChar() - If k<>0 And m_focus<>Null And m_focus.enabled And m_focus.consumes + If k<>0 And m_focus<>Null And m_focus.enabled And m_focus.consumes&TWidget.HAS_KEY=TWidget.HAS_KEY m_focus.HandleKey(k) EndIf + + If drag + If m_dragging + m_dragobj.HandleDrag(MouseX()-m_dragx,MouseY()-m_dragy) + m_dragx=MouseX() + m_dragy=MouseY() + Else + If w<>Null And w.consumes&TWidget.HAS_DRAG=TWidget.HAS_DRAG + m_dragging=True + m_dragx=MouseX() + m_dragy=MouseY() + m_dragobj=w + EndIf + EndIf + Else + m_dragging=False + m_dragobj=Null + EndIf End Method ' Private method @@ -975,6 +1396,117 @@ Function GUIMenu(title:String, options:String[], x:Int, y:Int, i:TImage=Null) Return -1 End Function +Rem +bbdoc: Displays a basic file selector. +returns: The selected file, or null for none. +about: @title is the title. @file is the initially selected file, and f set to null then no file is selected and the current directory displayed. +about: Set @save to true for a save dialog (the filename can be altered). +about: If @i is not null then this image is drawn as a mouse cursor. +EndRem +Function GUIFileSelect:String(title:String, file:String, save:Int, i:TImage=Null) + FlushKeys() + + Local oldpwd:String=CurrentDir() + Local dir:String + Local fn:String + Local sw:Int=GraphicsWidth() + Local sh:Int=GraphicsHeight() + Local fh:Int=TGUIFont.font.MaxHeight() + Local fw:Int=TGUIFont.font.MaxWidth() + Local rows:Int=(sh-fh*10)/fh + + If file<>Null And file<>"" + dir=ExtractDir(file) + + If dir="" + dir=oldpwd + EndIf + + If FileType(file)=FILETYPE_FILE + fn=StripDir(file) + Else + fn="" + EndIf + Else + dir=oldpwd + fn="" + EndIf + + Local gui:TGUIHandler=TGUIHandler.Create() + + TPanel.Create(gui,0,0,sw,sh) + TLabel.Create(gui,10,fh*0.5,title) + + Local list:TTextList=TTextList.Create(gui,20,fh*2,sw-40,rows) + Local scroll:TScrollbar=TScrollbar.Create(gui,list.x+list.w+5,list.y,10,list.h) + + Local fntext:TText + + If save + fntext=TText.Create(gui,20,sh-fh*6,fn,(sw-40)/fw,TText.FILENAME) + Else + fntext=TText.Create(gui,20,sh-fh*6,fn,(sw-40)/fw,TText.READONLY) + EndIf + + Local bw:Int=TGUIFont.font.MaxWidth()*9 + + Local cancel:TButton=TButton.Create(gui,sw-bw-20,sh-fh*3.5,bw,fh*2.5,"Cancel",Null) + Local ok:Tbutton=TButton.Create(gui,cancel.x-bw-10,cancel.y,bw,fh*2.5,"OK",Null) + + Local last:TDirEntry=Null + + ChangeDir(dir) + LoadDirEnts(list,scroll,rows) + + Repeat + Cls + + gui.EventLoop() + + TGUIFont.font.Draw(CurrentDir(),list.x,list.y+list.h+3) + + Local click:TWidget=gui.Clicked() + + If click=ok + Local ret:String=CurrentDir()+"/"+fntext.text + ChangeDir(oldpwd) + FlushKeys() + Return ret + EndIf + + If click=cancel Or KeyHit(KEY_ESCAPE) + ChangeDir(oldpwd) + FlushKeys() + Return Null + EndIf + + If list.GetSelectedItem()<>last + last=TDirEntry(list.GetSelectedItem()) + + If last.is_dir + ChangeDir(last.filename) + LoadDirEnts(list,scroll,rows) + fntext.text="" + last=Null + Else + fntext.text=last.filename + EndIf + EndIf + + ok.enabled=(fntext.text<>"") + + list.SetTopRow(scroll.GetValue()) + + If i<>Null + SetColor(255,255,255) + DrawImage(i,MouseX(),MouseY()) + EndIf + + Flip + FlushMem + Forever +End Function + Rem bbdoc: Handles a TGUIHandler as if it's a modal dialog. returns: True if @ok is pressed, False if @cancel or the ESCAPE key is pressed. ESCAPE is only allowed if nothing has the text focus. @@ -1018,6 +1550,7 @@ Function GUIDialog(gui:TGUIHandler, ok:TWidget, cancel:TWidget, i:TImage=Null) FlushMem Wend + FlushKeys() Return ok_pressed End Function @@ -1058,3 +1591,78 @@ Type TSplitText Return o End Function EndType + +Type TDirEntry Extends TTextListItem + Field path:String + Field dir:String + Field filename:String + Field size:Int + Field is_dir:Int + + Function Create:TDirEntry(path:String) + Local o:TDirEntry=New TDirEntry + o.path=path + o.dir=ExtractDir(path) + o.filename=StripDir(path) + o.is_dir=(FileType(path)=FILETYPE_DIR) + o.size=FileSize(path) + Return o + End Function + + Method Compare:Int(o:Object) + Local d:TDirEntry=TDirEntry(o) + + If d=Null + Return 0 + EndIf + + If is_dir=d.is_dir + Return filename.Compare(d.filename) + Else + If is_dir + Return -1 + Else + Return 1 + EndIf + EndIf + + Return 0 + End Method + + Method Columns:Int() + Return 2 + End Method + + Method ColumnOffset:Int(i:Int) + If i=0 + Return 0 + Else + Return GraphicsWidth()-TGUIFont.font.MaxWidth()*20 + EndIf + End Method + + Method ColumnData:String(i:Int) + If i=0 + Return filename + Else + If is_dir + Return "DIR" + Else + Return size+" bytes" + EndIf + EndIf + End Method +End Type + +Function LoadDirEnts(list:TTextList, scroll:TScrollbar, rows:Int) + Local d:TList=CreateList() + + For Local s:String=EachIn LoadDir(CurrentDir(),False) + d.AddLast(TDirEntry.Create(CurrentDir()+"/"+s)) + Next + + d.Sort() + + list.SetOptions(d.ToArray()) + scroll.SetBar(0,Max(1,d.Count()-rows/2),1) +End Function -- cgit v1.2.3