If you are not using a User Interface Plugin ( RAPA or ROYALE ), have a look at my post "Improved Simple Gadget Instructions". There is a menu system in there that you maybe able to use/adapt for your needs.
Code: Select all
@DISPLAY 1, {Width=800, Height=400, Color=#SILVER, Title=" by Leo den Hollander "}
; Escape ******************************************************************************************
EscapeQuit(True)
; ************************************************************************************************
; Gadget Include
; Setup File - required for all gadgets
; By Leo den Hollander
; *************************************************************************************************
; Global declares----------------------------------------------------------------------------------
Global GID ; Gadget ID
Global Gadget_i ; Current Gadget
Global Type ; Type of Gadget
Global DropdownGadget
Global CycleSel ; Selected Cycle Item
Global ColumnSel ; Selected Table Column
Global RowSel ; Selected Table Row
Global DropSel ; Dropdown List selection
Global MenuSel ; Menu Selection
Global PanelGadgetNo ; PanelGadget number
Global Font$ = #SANS ; Default Gadget Font
Global Size = 18 ; Default gadget Size
Global ScreenC = #SILVER ; Default Screen Color
Global GBC = #SILVER ; Default Gadget Background Color
Global GFC = #BLACK ; Default Gadget Front Color (Font)
Global MaxGadgets = 45 ; Max number Gadgets used in program (need to set manualy)
; Gadget Tables -----------------------------------------------------------------------------------
Dim Gadget[50][25]
Dim GadgetItem[50][20][10]
Dim GadgetMenu[50][20]
; Constant decares --------------------------------------------------------------------------------
Const #GadX = 1
Const #GadY = 2
Const #GadW = 3
Const #GadH = 4
Const #GadText = 5
Const #GadFont = 6
Const #GadFontSize = 7
Const #GadFontColor = 8
Const #GadBackColor = 9
Const #GadState = 10
Const #GadType = 11
Const #GadGroup = 12
Const #GadCount = 13
Const #GadLow = 14
Const #GadHigh = 15
Const #GadRow = 16
Const #GadCol = 17
Const #GadSelrow = 18
Const #GadSelCol = 19
Const #GadDisable = 20
Const #DropItems = 998
Const #MenuItem = 999
; Gadget Control Graphics -------------------------------------------------------------------------
UpArrow = {0,10,5,0,10,10,0,10}
DownArrow = {0,0,10,0,5,10,0,0}
Cross = {0,0,1,0,6,5,7,5,12,0,13,0,13,1,8,6,8,7,13,12,13,13,12,13,7,8,6,8,1,13,0,13,0,12,5,7,5,6,0,1,0,0}
CycleArrow = {8,1,8,8,1,8,8,1}
ColorTable = {16,#YELLOW,#LIME,#OLIVE,#GREEN,#AQUA,#TEAL,#BLUE,#NAVY,#PURPLE,#FUCHSIA,#MAROON,#RED,#WHITE,#SILVER,#GRAY,#BLACK}
EnableLayers
; ************************************************************************************************
; Gadget Include
; Gadget Text, Color, Font and Fontsize File
; By Leo den Hollander
; *************************************************************************************************
; Set Gadget Text ---------------------------------------------------------------------------------
Function SetGadgetText(GID,Text$)
SelectLayer("Gadget" .. StrStr(GID))
SetFillStyle(#FILLCOLOR)
SetFont(Gadget[GID][#GadFont], Gadget[GID][#GadFontSize])
SetFontColor(Gadget[GID][#GadFontColor])
Gadget[GID][#GadText] = Text$
If Gadget[GID][#GadType] = 1 Or Gadget[GID][#GadType] = 2
Box(1, 1, Gadget[GID][#GadW] - 2, Gadget[GID][#GadH] - 2, Gadget[GID][#GadBackColor])
Data$ = Text$
Local TW = TextWidth(Data$)
If TW > Gadget[GID][#GadW] - 6
While TW > Gadget[GID][#GadW] - 17
Data$ = UnleftStr(Data$, 1)
TW = TextWidth(Data$)
Wend
TextOut(4, 3, Data$ .. "...")
Else
TextOut(4, 3, Data$)
EndIf
ElseIf Gadget[GID][#GadType] = 3
TW = TextWidth(Text$) ; size of text on gadget face
Local BW =(Gadget[GID][#GadW] / 2) - (TW / 2) ; Centre text position
Local TH = TextHeight(Gadget[GID][#GadText]) ; size of text on gadget face
Local BH =(Gadget[GID][#GadH] / 2) -( TH / 2)
Box(2, 2, Gadget[GID][#GadW] - 4, Gadget[GID][#GadH] - 4, Gadget[GID][#GadBackColor])
TextOut(BW,BH,Gadget[GID][#GadText])
ElseIf Gadget[GID][#GadType] = 4 Or Gadget[GID][#GadType] = 5
Box(29, 2, Gadget[GID][#GadW] , Gadget[GID][#GadH], Gadget[GID][#GadBackColor])
TextOut(30, 3, Gadget[GID][#GadText])
EndIf
EndSelect
EndFunction
; Set Gadget Font colour ------------------------------------------------------------------------
Function SetGadgetFColor(GID,FC)
Gadget[GID][#GadFontColor] = FC
SetGadgetText(GID, Gadget[GID][#GadText])
EndFunction
; Set Gadget Background colour --------------------------------------------------------------------
Function SetGadgetBColor(GID,BC)
Gadget[GID][#GadBackColor] = BC
SetGadgetText(GID, Gadget[GID][#GadText])
EndFunction
; ************************************************************************************************
; Gadget Include
; Ladbel Gadget File
; By Leo den Hollander
; *************************************************************************************************
; 1 Label Gadget ----------------------------------------------------------------------------------
Function GadgetLabel(GID, x, y, w, h, Text$, Border)
Gadget$ = "Gadget"..StrStr(GID)
SetFont(Font$, Size)
SetFontColor(GFC)
CreateLayer(x, y, w, h,{Color = ScreenC, Name = Gadget$})
SelectLayer(Gadget$)
SetFillStyle(#FILLCOLOR)
Box(0, 0, w, h, ScreenC)
TextOut(4, 3, Text$)
EndSelect
If Border = 1
SetLayerBorder(Gadget$, True, #BLACK, 1)
ElseIf Border = 2
SetLayerBorder(Gadget$, True, #BLACK, 1)
SelectLayer(Gadget$)
Line(0, 0, w-1, 0,RGB(160, 160, 160)) ; Inner bevel
Line(0, 0, 0, h-1,RGB(160, 160, 160)) ; Inner bevel
Line(w-1, 0, w-1, h,RGB(224, 224, 224)) ; Inner bevel
Line(0, h-1, w, h-1,RGB(224, 224, 224)) ; Inner bevel
EndSelect
EndIf
MakeButton(GID, #SIMPLEBUTTON, x, y, w, h, Gadgets)
Gadget[GID][#GadX] = x
Gadget[GID][#GadY] = y
Gadget[GID][#GadW] = w
Gadget[GID][#GadH] = h
Gadget[GID][#GadText] = Text$
Gadget[GID][#GadFont] = Font$
Gadget[GID][#GadFontSize] = Size
Gadget[GID][#GadFontColor] = GFC
Gadget[GID][#GadBackColor] = GBC
Gadget[GID][#GadState] = 0
Gadget[GID][#GadType] = 1
Gadget[GID][#GadGroup] = 0
Gadget[GID][#GadCount] = 0
Gadget[GID][#GadLow] = 0
Gadget[GID][#GadHigh] = 0
Gadget[GID][#GadRow] = 0
Gadget[GID][#GadCol] = 0
Gadget[GID][#GadDisable] = 0
If PanelGadgetNo > 0
PID = PID + 1
GadgetItem[PanelGadgetNo][PanelNo][PID] = GID
GadgetItem[PanelGadgetNo][PanelNo][0] = GadgetItem[PanelGadgetNo][PanelNo][0] +1
HideLayer(Gadget$)
EndIf
EndFunction
; ************************************************************************************************
; Gadget Include
; Button Gadget File
; By Leo den Hollander
; *************************************************************************************************
; 3 Button Gadget -----------------------------------------------------------------------------------
Function GadgetButton(GID, x, y, w, h, Text$)
Gadget$ = "Gadget"..StrStr(GID)
SetFont(Font$,Size)
SetFontColor(GFC)
Local TW = TextWidth(Text$) ; size of text on gadget face
Local BW =(w / 2) - (TW / 2) ; Centre text position
Local TH = TextHeight(Text$) ; size of text on gadget face
Local BH =(h / 2) - (TH / 2)
CreateLayer(x,y,w,h,{Color = $AAAAAA, Name = Gadget$})
SelectLayer(Gadget$)
SetFillStyle(#FILLCOLOR)
Box( 0, 0, w, h, RGB(176, 176, 176))
Line(0, 0, w-1,0, RGB(224,224,224)) ; Inner bevel
Line(0, 0, 0, h-1,RGB(224,224,224)) ; Inner bevel
Line(w-1,0, w-1,h ,RGB(160, 160, 160)) ; Inner bevel
Line(0, h-1,w, h-1,RGB(160, 160, 160)) ; Inner bevel
TextOut(BW, BH, Text$)
EndSelect
SetLayerBorder(Gadget$, True, #BLACK, 1)
MakeButton(GID, #SIMPLEBUTTON, x, y, w, h, Button)
Gadget[GID][#GadX] = x
Gadget[GID][#GadY] = y
Gadget[GID][#GadW] = w
Gadget[GID][#GadH] = h
Gadget[GID][#GadText] = Text$
Gadget[GID][#GadFont] = Font$
Gadget[GID][#GadFontSize] = Size
Gadget[GID][#GadFontColor] = GFC
Gadget[GID][#GadBackColor] = GBC
Gadget[GID][#GadState] = 0
Gadget[GID][#GadType] = 3
Gadget[GID][#GadGroup] = 0
Gadget[GID][#GadCount] = 0
Gadget[GID][#GadLow] = 0
Gadget[GID][#GadHigh] = 0
Gadget[GID][#GadRow] = 0
Gadget[GID][#GadCol] = 0
Gadget[GID][#GadDisable] = 0
If PanelGadgetNo > 0
PID = PID + 1
GadgetItem[PanelGadgetNo][PanelNo][PID] = GID
GadgetItem[PanelGadgetNo][PanelNo][0] = GadgetItem[PanelGadgetNo][PanelNo][0] +1
HideLayer(Gadget$)
DisableButton(GID)
Gadget[GID][#GadDisable] = 1
EndIf
EndFunction
; ************************************************************************************************
; Gadget Include
; Menu Gadget File
; By Leo den Hollander
; *************************************************************************************************
; 15 Menu Gadget Setup ==============================================================================
Function MenuGadget(GID,MenuTitle$,MenuItem$)
SetFont(#SANS,Size)
array, MenuTitle_i = SplitStr(MenuTitle$,"|")
GadgetMenu[GID][0] = MenuTitle_i
SetFillStyle(#FILLCOLOR)
Local MenuSpacing_i = 1
For Local t = 1 To GadgetMenu[GID][0]
GadgetMenu[GID][t] = MenuSpacing_i
Local TW = TextWidth(array[t-1])
TW = TW + 20
MenuSpacing_i = MenuSpacing_i + TW + 1
Next
GadgetMenu[GID][MenuTitle_i+1] = MenuSpacing_i
CreateLayer(1,1,MenuSpacing_i,25,{Color = #GRAY, Name = "MenuTitle"})
SelectLayer("MenuTitle")
For t = 1 To GadgetMenu[GID][0]
Local TW = TextWidth(array[t-1])
TW = TW + 20
SetFillStyle(#FILLCOLOR)
Box(GadgetMenu[GID][t], 1, TW, 23, $AAAAAA)
TextOut(GadgetMenu[GID][t]+10,2,array[t-1])
Next
EndSelect
array, MenuItems_i = SplitStr(MenuItem$,"|")
MenuTileNo_i = 1
MenuItemNo_i = 1
For Local I = 1 To MenuItems_i
If array[I-1] <> ""
GadgetItem[GID][MenuTileNo_i][MenuItemNo_i] = array[I-1]
MenuItemNo_i = MenuItemNo_i + 1
Else
GadgetItem[GID][MenuTileNo_i][0] = MenuItemNo_i
MenuTileNo_i = MenuTileNo_i + 1
MenuItemNo_i = 1
EndIf
Next
For Local M = 1 To GadgetMenu[GID][0]
Menu$ = "Menu"..StrStr(m)
CreateLayer(GadgetMenu[GID][M],27,150,(Val(GadgetItem[GID][M][0])-1)*22,{Color = $AAAAAA, Name = Menu$, Hidden = True})
SelectLayer(Menu$)
SetFont(#SANS,20)
SetFontColor(#BLACK)
For t = 1 To Val(GadgetItem[GID][M][0])-1
TextOut(10,(t-1)*22,GadgetItem[GID][M][t])
SetFillStyle(#FILLNONE)
Line(1,(t)*22,150,(t)*22,$777777)
Next
EndSelect
Next
MakeButton(GID, #SIMPLEBUTTON, 1, 1, MenuSpacing_i, 25, Menu)
Gadget[GID][#GadX] = x
Gadget[GID][#GadY] = y
Gadget[GID][#GadW] = w
Gadget[GID][#GadH] = h
Gadget[GID][#GadText] = MenuTitle$
Gadget[GID][#GadFont] = Font$
Gadget[GID][#GadFontSize] = Size
Gadget[GID][#GadFontColor] = GFC
Gadget[GID][#GadBackColor] = GBC
Gadget[GID][#GadState] = 0
Gadget[GID][#GadType] = 15
Gadget[GID][#GadGroup] = 0
Gadget[GID][#GadCount] = MenuTitle_i
Gadget[GID][#GadLow] = 0
Gadget[GID][#GadHigh] = 0
Gadget[GID][#GadRow] = 0
Gadget[GID][#GadCol] = 0
Gadget[GID][#GadDisable] =0
GadgetItem[GID][0][0] = ""
EndFunction
Function p_EventMenu(msg)
Gadget_i = msg.id
Local DPosX = MouseX()
Local DPosY = MouseY()
For Local t = 1 To Val(GadgetMenu[Gadget_i][0])
If DPosY < 25
If DPosX > GadgetMenu[Gadget_i][t] And DPosX < GadgetMenu[Gadget_i][t+1]
MenuSel = t
EndIf
EndIf
Next
Switch msg.action
Case "OnMouseDown":
For Local M = 1 To GadgetMenu[Gadget_i][0]
Menu$ = "Menu"..StrStr(M)
HideLayer(Menu$)
Next
ShowLayer("Menu"..StrStr(MenuSel))
SetLayerZPos("Menu"..StrStr(MenuSel),0)
SetLayerShadow("Menu"..StrStr(MenuSel), True)
MakeButton(#MenuItem, #SIMPLEBUTTON, GadgetMenu[Gadget_i][MenuSel],26,150,Val(GadgetItem[Gadget_i][MenuSel][0]-1)*22,GetMenuItem)
EndSwitch
EndFunction
Function p_GetMenuItem(msg)
Switch msg.action
Case "OnMouseUp":
Local DPosY = MouseY()
ITS = Int((DPosY - 26) / 22) + 1
MIA = 0
If MenuSel = 2 Then MIA=Val(GadgetItem[Gadget_i][1][0])-1
If MenuSel = 3 Then MIA=Val(GadgetItem[Gadget_i][2][0])-1+Val(GadgetItem[Gadget_i][1][0])-1
If MenuSel = 4 Then MIA=Val(GadgetItem[Gadget_i][3][0])-1+Val(GadgetItem[Gadget_i][1][0])-1+Val(GadgetItem[Gadget_i][2][0])-1
If MenuSel = 5 Then MIA=Val(GadgetItem[Gadget_i][4][0])-1+Val(GadgetItem[Gadget_i][1][0])-1+Val(GadgetItem[Gadget_i][2][0])-1+Val(GadgetItem[Gadget_i][3][0])-1
If MenuSel = 6 Then MIA=Val(GadgetItem[Gadget_i][5][0])-1+Val(GadgetItem[Gadget_i][1][0])-1+Val(GadgetItem[Gadget_i][2][0])-1+Val(GadgetItem[Gadget_i][3][0])-1+Val(GadgetItem[Gadget_i][4][0])-1
MITS = ITS + MIA
HideLayer("menu"..StrStr(MenuSel))
DeleteButton(#MenuItem)
Switch MITS
Case 1
SetGadgetText(4,"Menu 1" )
Case 2
SetGadgetText(4,"Menu 2" )
Case 3
SetGadgetText(4,"Menu 3" )
Case 4
SetGadgetText(4,"Menu 4" )
Case 5
Case 6
Case 7
Case 8
Case 9
Case 10
EndSwitch
Case "OnMouseOut":
HideLayer("Menu"..StrStr(MenuSel))
DeleteButton(#MenuItem)
EndSwitch
EndFunction
; ************************************************************************************************
; Gadget Include
; EventButton File
; By Leo den Hollander
; *************************************************************************************************
Function p_EventButton(msg)
Gadget$ = "Gadget"..StrStr(msg.id)
Switch msg.action
Case "OnMouseDown":
SetLayerBorder(Gadget$, True, #GRAY, 1)
p_Actions(msg.id)
Case "OnMouseOut":
SetLayerBorder(Gadget$, True, #BLACK, 1)
Case "OnMouseUp":
SetLayerBorder(Gadget$, True, #BLACK, 1)
Case "OnMouseOver":
SetLayerBorder(Gadget$, True, #BLUE, 1)
EndSwitch
EndFunction
; ************************************************************************************************
; Gadget Include
; EventGadget File
; By Leo den Hollander
; *************************************************************************************************
Function p_EventGadget(msg)
Type = Gadget[msg.id][#GadType]
Gadget_i = msg.id
Gadget$ = "Gadget"..StrStr(Gadget_i)
Local WD
Local HT
If Type = 4 Or Type = 5
WD = 26
HT = 26
EndIf
If Type = 6 Or Type = 7
WD = Gadget[msg.id][#GadW]
HT = 26
EndIf
If Type = 8 Or Type = 9 Or Type = 14
WD = Gadget[msg.id][#GadW]
HT = Gadget[msg.id][#GadH]
EndIf
Switch msg.action
Case "OnMouseDown":
Switch Type
Case 2:
Gadget[Gadget_i][#GadState] = 1
SetLayerBorder(Gadget$, True, #GRAY, 1)
SelectLayer(Gadget$)
SetFillStyle(#FILLCOLOR)
If RightStr(Gadget[Gadget_i][#GadText],1)="|" Then Gadget[Gadget_i][#GadText] = ReplaceStr(Gadget[Gadget_i][#GadText],"|","")
Box(2, 2,Gadget[Gadget_i][#GadW] -4, Gadget[Gadget_i][#GadH] -4, Gadget[Gadget_i][#GadBackColor])
TextOut(4, 3, Gadget[Gadget_i][#GadText]..Chr(124))
Gadget[Gadget_i][#GadText] = Gadget[Gadget_i][#GadText]..Chr(124)
SetFormStyle(#NORMAL)
EndSelect
Case 4:
p_SetOption(msg.id)
Case 5:
p_SetCheck(msg.id)
Case 6:
p_SetCycle(msg.id)
Case 7:
p_SetSpin(msg.id)
Case 8:
p_SetSlider(msg.id)
Case 9:
p_SetTable(msg.id)
Case 14:
p_SetColor(msg.id)
EndSwitch
Case "OnMouseOut": ; ** highlight button on mouse out
Switch Type
Case 2:
Gadget[Gadget_i][#GadState] = 0
SelectLayer(Gadget$)
SetFillStyle(#FILLCOLOR)
If RightStr(Gadget[Gadget_i][#GadText],1)="|" Then Gadget[Gadget_i][#GadText] = ReplaceStr(Gadget[Gadget_i][#GadText],"|","")
Box(2, 2,Gadget[Gadget_i][#GadW] -4, Gadget[Gadget_i][#GadH] -4, Gadget[Gadget_i][#GadBackcolor])
TextOut(4, 3, Gadget[Gadget_i][#GadText])
Gadget[Gadget_i][#GadText] = Gadget[Gadget_i][#GadText]
SetFormStyle(#NORMAL)
EndSelect
SetLayerBorder(Gadget$, True, #BLACK, 1)
Case 4:
SelectLayer("Gadget"..StrStr(Gadget_i))
SetFillStyle(#FILLNONE)
Box(0, 0, WD, HT,#BLACK)
SetFormStyle(#NORMAL)
EndSelect
Case 5:
SelectLayer("Gadget"..StrStr(Gadget_i))
SetFillStyle(#FILLNONE)
Box(0, 0, WD, HT,#BLACK)
SetFormStyle(#NORMAL)
EndSelect
Case 6:
SetLayerBorder(Gadget$, True, #BLACK, 1)
Case 7:
SetLayerBorder(Gadget$, True, #BLACK, 1)
Case 9:
SetLayerBorder(Gadget$, True, #BLACK, 1)
Case 14:
SetLayerBorder(Gadget$, True, #BLACK, 1)
EndSwitch
Case "OnMouseUp": ; ** highlight button on mouse up
Switch Type
Case 4:
SelectLayer("Gadget"..StrStr(Gadget_i))
SetFillStyle(#FILLNONE)
Box(0, 0, WD, HT,#BLACK)
SetFormStyle(#NORMAL)
EndSelect
p_Actions(msg.id)
Case 5:
SelectLayer("Gadget"..StrStr(Gadget_i))
SetFillStyle(#FILLNONE)
Box(0, 0, WD, HT,#BLACK)
SetFormStyle(#NORMAL)
EndSelect
p_Actions(msg.id)
Case 6:
SetLayerBorder(Gadget$, True, #BLACK, 1)
p_Actions(msg.id)
Case 7:
SetLayerBorder(Gadget$, True, #BLACK, 1)
p_Actions(msg.id)
Case 9:
SetLayerBorder(Gadget$, True, #BLACK, 1)
p_Actions(msg.id)
Case 14:
SetLayerBorder(Gadget$, True, #BLACK, 1)
p_Actions(msg.id)
EndSwitch
;If Type = 4 Or Type = 5 Or Type = 6 Or Type = 7 Or Type = 8 Or Type = 9 Or Type = 13
; p_Actions(msg.id)
;EndIf
Case "OnMouseOver": ; ** highlight button on mouse over
Switch Type
Case 4:
SelectLayer("Gadget"..StrStr(Gadget_i))
SetFillStyle(#FILLNONE)
Box(0, 0, WD, HT,#GRAY)
SetFormStyle(#NORMAL)
EndSelect
Case 5:
SelectLayer("Gadget"..StrStr(Gadget_i))
SetFillStyle(#FILLNONE)
Box(0, 0, WD, HT,#GRAY)
SetFormStyle(#NORMAL)
EndSelect
Case 6:
SetLayerBorder(Gadget$, True, #GRAY, 1)
Case 7:
SetLayerBorder(Gadget$, True, #GRAY, 1)
Case 9:
SetLayerBorder(Gadget$, True, #GRAY, 1)
Case 14:
SetLayerBorder(Gadget$, True, #GRAY, 1)
EndSwitch
EndSwitch
EndFunction
; #################################################################################################
Function p_Actions(id)
SetGadgetText(4,"Gadget " .. id)
EndFunction
; #################################################################################################
; Events Setup ------------------------------------------------------------------------------------
Menu = {OnMouseDown = p_EventMenu, OnMouseOver = p_EventMenu}
GetMenuItem = {OnMouseUp = p_GetMenuItem, OnMouseOut = p_GetMenuItem, OnMouseOver = p_GetMenuItem}
Button = {OnMouseOver = p_EventButton, OnMouseOut = p_EventButton, OnMouseDown = p_EventButton, OnMouseUp = p_EventButton}
Gadgets = {OnMouseOver = p_EventGadget, OnMouseOut = p_EventGadget, OnMouseDown = p_EventGadget, OnMouseUp = p_EventGadget}
MenuGadget( 1,"FILE|TOOLS|ABOUT|HELP", "New|Open|Close|Save|SaveAs||Add|Edit|Remove||About Program||Help|")
GadgetButton( 2, 250, 1, 120, 25, "Button")
GadgetButton( 3, 380, 1, 120, 25, "Button")
GadgetLabel( 4, 200, 40, 200, 25, "Label Gadget Border = 0", 0)
GadgetLabel( 5, 200, 80, 200, 25, "Label Gadget Border = 1", 1)
GadgetLabel( 6, 200, 120, 200, 25, "Label Gadget Border = 2", 2)
GadgetLabel( 7, 200, 160, 200, 25, "Label Gadget Background", 0)
GadgetLabel( 8, 150, 200, 250, 25, "Label Foreground & Background", 1)
SetGadgetBColor( 7, RGB(200,230,190))
SetGadgetBColor( 8, RGB(160,210,240))
SetGadgetFColor( 8, RGB(255,000,255))
; Main Program Loop *******************************************************************************
Repeat
WaitEvent
Forever