├── Nimforms ├── apimodule.nim ├── button.nim ├── calendar.nim ├── checkbox.nim ├── colors.nim ├── com.nim ├── combobox.nim ├── commons.nim ├── comtypes.nim ├── contextmenu.nim ├── controls.nim ├── datetimepicker.nim ├── dialogs.nim ├── events.nim ├── font.nim ├── forms.nim ├── graphics.nim ├── groupbox.nim ├── label.nim ├── listbox.nim ├── listview.nim ├── menu.nim ├── nimforms.nim ├── numberpicker.nim ├── progressbar.nim ├── propertygrid.nim ├── radiobutton.nim ├── textbox.nim ├── trackbar.nim ├── trayicon.nim ├── treeview.nim ├── typemodule.nim ├── widestring.nim └── winmessages.nim ├── README.md ├── app.exe.manifest ├── app.nim ├── nficon.ico └── nimforms_130824.jpg /Nimforms/button.nim: -------------------------------------------------------------------------------- 1 | 2 | # button module Created on 27-Mar-2023 01:56 PM 3 | 4 | #[========================================Button Docs======================================== 5 | constructor - newButton(): Button 6 | Properties 7 | All props inherited from Control type 8 | 9 | Functions 10 | createHandle - Create the handle of button 11 | setGradientColor - Set gradient back color 12 | 13 | Events 14 | All events inherited from Control type. 15 | 16 | ======================================================================================================]# 17 | 18 | # Constants 19 | const 20 | BS_PUSHBUTTON = 0x00000000 21 | BS_DEFPUSHBUTTON = 0x00000001 22 | BS_CHECKBOX = 0x00000002 23 | BS_AUTOCHECKBOX = 0x00000003 24 | BS_RADIOBUTTON = 0x00000004 25 | BS_3STATE = 0x00000005 26 | BS_AUTO3STATE = 0x00000006 27 | BS_GROUPBOX = 0x00000007 28 | BS_USERBUTTON = 0x00000008 29 | BS_AUTORADIOBUTTON = 0x00000009 30 | BS_PUSHBOX = 0x0000000A 31 | BS_OWNERDRAW = 0x0000000B 32 | BS_TYPEMASK = 0x0000000F 33 | BS_LEFTTEXT = 0x00000020 34 | BS_TEXT = 0x00000000 35 | BS_ICON = 0x00000040 36 | BS_BITMAP = 0x00000080 37 | BS_LEFT = 0x00000100 38 | BS_RIGHT = 0x00000200 39 | BS_CENTER = 0x00000300 40 | BS_TOP = 0x00000400 41 | BS_BOTTOM = 0x00000800 42 | BS_VCENTER = 0x00000C00 43 | BS_PUSHLIKE = 0x00001000 44 | BS_MULTILINE = 0x00002000 45 | BS_NOTIFY = 0x00004000 46 | BS_FLAT = 0x00008000 47 | BS_RIGHTBUTTON = BS_LEFTTEXT 48 | BN_CLICKED = 0 49 | BN_PAINT = 1 50 | BN_HILITE = 2 51 | BN_UNHILITE = 3 52 | BN_DISABLE = 4 53 | BN_DOUBLECLICKED = 5 54 | BN_PUSHED = BN_HILITE 55 | BN_UNPUSHED = BN_UNHILITE 56 | BN_DBLCLK = BN_DOUBLECLICKED 57 | BN_SETFOCUS = 6 58 | BN_KILLFOCUS = 7 59 | BM_GETCHECK = 0x00F0 60 | BM_SETCHECK = 0x00F1 61 | BM_GETSTATE = 0x00F2 62 | BM_SETSTATE = 0x00F3 63 | BM_SETSTYLE = 0x00F4 64 | BM_CLICK = 0x00F5 65 | BM_GETIMAGE = 0x00F6 66 | BM_SETIMAGE = 0x00F7 67 | BM_SETDONTCLICK = 0x00f8 68 | MOUSECLICKFLAG = 0b1 69 | MOUSEOVERFLAG = 0b1000000 70 | ROUND_CURVE = 5 71 | 72 | var btnCount = 1 73 | 74 | # Forward declaration 75 | proc btnWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 76 | proc createHandle*(this: Button) 77 | 78 | # Button constructor 79 | proc newButton*(parent: Form, txt: string = "", x: int32 = 10, y: int32 = 10, 80 | w: int32 = 110, h: int32 = 34, evtFn: EventHandler = nil): Button = 81 | new(result) 82 | result.mKind = ctButton 83 | result.mClassName = cast[LPCWSTR](BtnClass[0].addr) 84 | result.mName = "Button_" & $btnCount 85 | result.mParent = parent 86 | result.mXpos = x 87 | result.mYpos = y 88 | result.mWidth = w 89 | result.mHeight = h 90 | # result.mFont = parent.mFont 91 | result.mHasFont = true 92 | result.mHasText = true 93 | result.mStyle = WS_CHILD or BS_NOTIFY or WS_TABSTOP or WS_VISIBLE or BS_PUSHBUTTON 94 | result.mText = (if txt == "": "Button_" & $btnCount else: txt) 95 | result.cloneParentFont() 96 | result.mWtext = newWideString(result.mText) 97 | parent.mControls.add(result) 98 | btnCount += 1 99 | if evtFn != nil: result.onClick = evtFn 100 | if parent.mCreateChilds: result.createHandle() 101 | 102 | 103 | # Create button's hwnd 104 | proc createHandle*(this: Button) = 105 | this.createHandleInternal() 106 | if this.mHandle != nil: 107 | this.setSubclass(btnWndProc) 108 | this.setFontInternal() 109 | 110 | method autoCreate(this: Button) = this.createHandle() 111 | 112 | 113 | # Set necessery data for a flat colored button 114 | proc flatDrawSetData(this: var FlatDraw, clr: Color) = 115 | let adj : float = (if clr.isDark: 1.5 else: 1.15) 116 | this.defBrush = CreateSolidBrush(clr.cref) 117 | this.hotBrush = CreateSolidBrush(clr.getChangedColorRef(adj)) 118 | this.defPen = CreatePen(PS_SOLID, 1, clr.getChangedColorRef(0.6)) 119 | this.hotPen = CreatePen(PS_SOLID, 1, clr.getChangedColorRef(0.3)) 120 | this.isActive = true 121 | 122 | # Overriding Control's property because, button class needs a different treatment 123 | proc `backColor=`*(this: Button, clr: uint) = 124 | this.mBackColor = newColor(clr) 125 | this.mFDraw.flatDrawSetData(this.mBackColor) 126 | if (this.mDrawMode and 2) != 2: this.mDrawMode += 2 127 | this.checkRedraw() 128 | 129 | # Set necessery data for a gradient colored button. 130 | proc gradDrawSetData(this: var GradDraw, c1, c2: uint) = 131 | this.isActive = true 132 | this.gcDef.c1 = newColor(c1) 133 | this.gcDef.c2 = newColor(c2) 134 | let hotAdj1 = (if this.gcDef.c1.isDark(): 1.5 else : 1.2) 135 | let hotAdj2 = (if this.gcDef.c2.isDark(): 1.5 else: 1.2) 136 | this.gcHot.c1 = this.gcDef.c1.getChangedColor(hotAdj1) 137 | this.gcHot.c2 = this.gcDef.c2.getChangedColor(hotAdj2) 138 | this.defPen = CreatePen(PS_SOLID, 1, this.gcDef.c1.getChangedColorRef(0.6)) 139 | this.hotPen = CreatePen(PS_SOLID, 1, this.gcHot.c1.getChangedColorRef(0.3)) 140 | if this.defBrush != nil: 141 | this.defBrush = nil 142 | if this.hotBrush != nil: 143 | this.hotBrush = nil 144 | 145 | # Set gradient colors for this button. 146 | proc setGradientColor*(this: Button, clr1, clr2: uint) = 147 | this.mGDraw.gradDrawSetData(clr1, clr2) 148 | if (this.mDrawMode and 4) != 4: this.mDrawMode += 4 149 | this.checkRedraw() 150 | 151 | # Helper function for drawing a flat color button. 152 | proc paintFlatBtnRoundRect(dc: HDC, rc: RECT, hbr: HBRUSH, pen: HPEN): LRESULT = 153 | SelectObject(dc, pen); 154 | SelectObject(dc, hbr); 155 | RoundRect(dc, rc.left, rc.top, rc.right, rc.bottom, ROUND_CURVE, ROUND_CURVE); 156 | FillPath(dc); 157 | result = CDRF_NOTIFYPOSTPAINT 158 | 159 | # Helper function for drawing text on a button. 160 | proc drawTextColor(this: Button, ncd: LPNMCUSTOMDRAW): LRESULT = 161 | SetTextColor(ncd.hdc, this.mForeColor.cref) 162 | SetBkMode(ncd.hdc, 1) 163 | DrawTextW(ncd.hdc, &this.mWtext, this.mWtext.wcLen, ncd.rc.unsafeAddr, this.mTxtFlag ) 164 | return CDRF_NOTIFYPOSTPAINT 165 | 166 | # Helper function dealing wm_notify message in a button's wndproc. 167 | proc drawBackColor(this: Button, ncd: LPNMCUSTOMDRAW): LRESULT = 168 | case ncd.dwDrawStage 169 | of CDDS_PREERASE: # This happens when the paint starts 170 | return CDRF_NOTIFYPOSTERASE # Telling the program to inform us after erase 171 | of CDDS_PREPAINT: # We get the notification after erase happened. 172 | if (ncd.uItemState and MOUSECLICKFLAG) == MOUSECLICKFLAG: 173 | return paintFlatBtnRoundRect(ncd.hdc, ncd.rc, this.mFDraw.defBrush, this.mFDraw.hotPen) 174 | elif (ncd.uItemState and MOUSEOVERFLAG) == MOUSEOVERFLAG: 175 | return paintFlatBtnRoundRect(ncd.hdc, ncd.rc, this.mFDraw.hotBrush, this.mFDraw.hotPen) 176 | else: 177 | return paintFlatBtnRoundRect(ncd.hdc, ncd.rc, this.mFDraw.defBrush, this.mFDraw.defPen) 178 | else: discard 179 | return CDRF_DODEFAULT 180 | 181 | # Helper function for drawing a gradient color button. 182 | proc paintGradientRound(nm: LPNMCUSTOMDRAW, gBrush: HBRUSH, pen: HPEN): LRESULT = 183 | # var gBrush = createGradientBrush(dc, rc, gc.c1, gc.c2) 184 | SelectObject(nm.hdc, pen) 185 | SelectObject(nm.hdc, gBrush) 186 | RoundRect(nm.hdc, nm.rc.left, nm.rc.top, nm.rc.right, nm.rc.bottom, 5, 5) 187 | FillPath(nm.hdc) 188 | # DeleteObject(gBrush) 189 | result = CDRF_DODEFAULT 190 | 191 | # Helper function dealing wm_notify message in a button's wndproc. 192 | proc drawGradientBackColor(this: Button, ncd: LPNMCUSTOMDRAW): LRESULT = 193 | case ncd.dwDrawStage 194 | of CDDS_PREERASE: return CDRF_NOTIFYPOSTERASE 195 | of CDDS_PREPAINT: 196 | if (ncd.uItemState and MOUSECLICKFLAG) == MOUSECLICKFLAG: 197 | if this.mGDraw.defBrush == nil: 198 | this.mGDraw.createGradientBrush(ncd.hdc, ncd.rc, gmClicked) 199 | return paintGradientRound(ncd, this.mGDraw.defBrush, this.mGDraw.hotPen) 200 | elif (ncd.uItemState and MOUSEOVERFLAG) == MOUSEOVERFLAG: 201 | if this.mGDraw.hotBrush == nil: 202 | this.mGDraw.createGradientBrush(ncd.hdc, ncd.rc, gmFocused) 203 | return paintGradientRound(ncd, this.mGDraw.hotBrush, this.mGDraw.hotPen) 204 | else: 205 | if this.mGDraw.defBrush == nil: 206 | this.mGDraw.createGradientBrush(ncd.hdc, ncd.rc, gmDefault) 207 | return paintGradientRound(ncd, this.mGDraw.defBrush, this.mGDraw.defPen) 208 | else: discard 209 | 210 | proc flatDrawFinalize(this: var FlatDraw) = 211 | DeleteObject(this.defBrush) 212 | DeleteObject(this.hotBrush) 213 | DeleteObject(this.defPen) 214 | DeleteObject(this.hotPen) 215 | echo "flat draw finished" 216 | 217 | proc gradDrowFinalize(this: var GradDraw) = 218 | DeleteObject(this.defBrush) 219 | DeleteObject(this.hotBrush) 220 | DeleteObject(this.defPen) 221 | DeleteObject(this.hotPen) 222 | echo "grad draw finished" 223 | 224 | # Deleting certain resources for this button. 225 | proc btnDtor(this: Button) = 226 | if this.mFDraw.isActive: 227 | this.mFDraw.flatDrawFinalize() 228 | 229 | if this.mGDraw.isActive: 230 | this.mGDraw.gradDrowFinalize() 231 | 232 | 233 | 234 | proc btnWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, 235 | scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 236 | case msg 237 | of WM_DESTROY: 238 | var this = cast[Button](refData) 239 | this.btnDtor() 240 | this.destructor() 241 | RemoveWindowSubclass(hw, btnWndProc, scID) 242 | 243 | of WM_LBUTTONDOWN: 244 | var this = cast[Button](refData) 245 | this.leftButtonDownHandler(msg, wpm, lpm) 246 | 247 | of WM_LBUTTONUP: 248 | var this = cast[Button](refData) 249 | this.leftButtonUpHandler(msg, wpm, lpm) 250 | 251 | of WM_RBUTTONDOWN: 252 | var this = cast[Button](refData) 253 | this.rightButtonDownHandler(msg, wpm, lpm) 254 | 255 | of WM_RBUTTONUP: 256 | var this = cast[Button](refData) 257 | this.rightButtonUpHandler(msg, wpm, lpm) 258 | 259 | of WM_MOUSEMOVE: 260 | var this = cast[Button](refData) 261 | this.mouseMoveHandler(msg, wpm, lpm) 262 | 263 | of WM_MOUSELEAVE: 264 | var this = cast[Button](refData) 265 | this.mouseLeaveHandler() 266 | 267 | of WM_CONTEXTMENU: 268 | var this = cast[Button](refData) 269 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 270 | 271 | of MM_NOTIFY_REFLECT: 272 | var this = cast[Button](refData) 273 | var ret : LRESULT= CDRF_DODEFAULT 274 | if this.mDrawMode > 0: 275 | var nmcd = cast[LPNMCUSTOMDRAW](lpm) 276 | case this.mDrawMode 277 | of 1: ret = this.drawTextColor(nmcd) # ForeColor only 278 | of 2: ret = this.drawBackColor(nmcd) # BackColor only 279 | of 3: 280 | discard this.drawBackColor(nmcd) # Back & Fore colors 281 | ret = this.drawTextColor(nmcd) 282 | of 4: ret = this.drawGradientBackColor(nmcd) # Gradient only 283 | of 5: #------------------------------------------------Gradient & fore colors 284 | discard this.drawGradientBackColor(nmcd) 285 | ret = this.drawTextColor(nmcd) 286 | else: discard 287 | return ret 288 | 289 | else: return DefSubclassProc(hw, msg, wpm, lpm) 290 | return DefSubclassProc(hw, msg, wpm, lpm) 291 | 292 | -------------------------------------------------------------------------------- /Nimforms/calendar.nim: -------------------------------------------------------------------------------- 1 | 2 | # calendar module Created on 29-Mar-2023 05:03 PM; Author kcvinker 3 | 4 | #[=========================================Calendar Docs======================================= 5 | Constructor - newCalendar*(parent: Form, x: int32 = 10, y: int32 = 10): Calendar 6 | Functions 7 | createHandle 8 | 9 | Properties 10 | All props inherited from Control type 11 | value : DateAndTime - See typemodule.nim 12 | viewMode : ViewMode - An enum, see typemodule.nim 13 | oldViewMode : ViewMode - Ad enum. 14 | noToday : bool 15 | shortDateNames : bool 16 | showWeekNumber : bool 17 | noTodayCircle : bool 18 | noTrailingDates: bool 19 | 20 | Events 21 | All events inherited from Control type 22 | EventHandler - proc(c: Control, e: EventArgs) 23 | onSelectionCommitted 24 | onValueChanged 25 | onViewChanged 26 | ==========================================================================================================]# 27 | 28 | 29 | 30 | # Constants 31 | const 32 | MCM_FIRST = 0x1000 33 | MCN_FIRST = cast[UINT](0-746) 34 | MCM_GETCALENDARGRIDINFO = MCM_FIRST+24 35 | MCM_GETCALID = MCM_FIRST+27 36 | MCM_SETCALID = MCM_FIRST+28 37 | MCM_SIZERECTTOMIN = MCM_FIRST+29 38 | MCM_SETCALENDARBORDER = MCM_FIRST+30 39 | MCM_GETCALENDARBORDER = MCM_FIRST+31 40 | MCM_SETCURRENTVIEW = MCM_FIRST+32 41 | MCN_SELCHANGE = MCN_FIRST-3 42 | MCN_GETDAYSTATE = MCN_FIRST+3 43 | MCN_SELECT = MCN_FIRST 44 | MCN_VIEWCHANGE = MCN_FIRST-4 45 | MCS_DAYSTATE = 0x1 46 | MCS_MULTISELECT = 0x2 47 | MCS_WEEKNUMBERS = 0x4 48 | MCS_NOTODAYCIRCLE = 0x8 49 | MCS_NOTODAY = 0x10 50 | MCS_NOTRAILINGDATES = 0x40 51 | MCS_SHORTDAYSOFWEEK = 0x80 52 | MCS_NOSELCHANGEONNAV = 0x100 53 | MCM_GETMINREQRECT = MCM_FIRST+9 54 | MCM_GETCURSEL = MCM_FIRST+1 55 | MCM_SETCURSEL = MCM_FIRST+2 56 | 57 | var calCount = 1 58 | let calClsName : array[14, uint16] = [0x53, 0x79, 0x73, 0x4D, 0x6F, 0x6E, 0x74, 0x68, 0x43, 0x61, 0x6C, 0x33, 0x32, 0] 59 | 60 | # Forward declaration 61 | proc calWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 62 | proc createHandle*(this: Calendar) 63 | 64 | proc newDateAndTime*(st: SYSTEMTIME): DateAndTime = 65 | result.year = int32(st.wYear) 66 | result.month = int32(st.wMonth) 67 | result.day = int32(st.wDay) 68 | result.hour = int32(st.wHour) 69 | result.minute = int32(st.wMinute) 70 | result.second = int32(st.wSecond) 71 | result.milliSecond = int32(st.wMilliseconds) 72 | result.dayOfWeek = cast[WeekDays](st.wDayOfWeek) 73 | 74 | proc makeSystemTime(dt: DateAndTime): SYSTEMTIME = 75 | result.wYear = WORD(dt.year) 76 | result.wMonth = WORD(dt.month) 77 | result.wDay = WORD(dt.day) 78 | result.wHour = WORD(dt.hour) 79 | result.wMinute = WORD(dt.minute) 80 | result.wSecond = WORD(dt.second) 81 | result.wMilliseconds = WORD(dt.milliSecond) 82 | result.wDayOfWeek = WORD(dt.dayOfWeek) 83 | 84 | 85 | 86 | # Calendar constructor 87 | proc newCalendar*(parent: Form, x: int32 = 10, y: int32 = 10): Calendar = 88 | new(result) 89 | result.mKind = ctCalendar 90 | result.mClassName = cast[LPCWSTR](calClsName[0].addr) 91 | result.mName = "Calendar_" & $calCount 92 | result.mParent = parent 93 | result.mXpos = x 94 | result.mYpos = y 95 | result.mWidth = 10 96 | result.mHeight = 10 97 | result.mStyle = WS_CHILD or WS_TABSTOP or WS_VISIBLE 98 | result.mViewMode = vmMonthView 99 | calCount += 1 100 | parent.mControls.add(result) 101 | if parent.mCreateChilds: result.createHandle() 102 | 103 | proc setCalStyle(this: Calendar) = 104 | if this.mShowWeekNum: this.mStyle = this.mStyle or MCS_WEEKNUMBERS 105 | if this.mNoTodayCircle: this.mStyle = this.mStyle or MCS_NOTODAYCIRCLE 106 | if this.mNoToday: this.mStyle = this.mStyle or MCS_NOTODAY 107 | if this.mNoTrailDates: this.mStyle = this.mStyle or MCS_NOTRAILINGDATES 108 | if this.mShortDateNames: this.mStyle = this.mStyle or MCS_SHORTDAYSOFWEEK 109 | 110 | proc setValueInternal(this: Calendar, st: SYSTEMTIME) = 111 | this.mValue = newDateAndTime(st) 112 | 113 | # Create Calendar's hwnd 114 | proc createHandle*(this: Calendar) = 115 | this.setCalStyle() 116 | this.createHandleInternal() 117 | if this.mHandle != nil: 118 | this.setSubclass(calWndProc) 119 | # this.setFontInternal() 120 | var rc: RECT 121 | this.sendMsg(MCM_GETMINREQRECT, 0, rc.unsafeAddr) 122 | SetWindowPos(this.mHandle, nil, this.mXpos, this.mYpos, rc.right, rc.bottom, SWP_NOZORDER) 123 | var st: SYSTEMTIME 124 | this.sendMsg(MCM_GETCURSEL, 0, st.unsafeAddr) 125 | this.setValueInternal(st) 126 | 127 | method autoCreate(this: Calendar) = this.createHandle() 128 | 129 | # Set the value property 130 | proc `value=`*(this: Calendar, dateValue: DateAndTime) {.inline.} = 131 | this.mValue = dateValue 132 | let stime = makeSystemTime(this.mValue) 133 | if this.mIsCreated: this.sendMsg(MCM_SETCURSEL, 0, stime) 134 | 135 | # Get the value property 136 | proc value*(this: Calendar): DateAndTime = this.mValue 137 | 138 | # Set the viewMode property 139 | proc `viewMode=`*(this: Calendar, value: ViewMode) {.inline.} = 140 | this.mViewMode = value 141 | if this.mIsCreated: this.sendMsg(MCM_SETCURRENTVIEW, 0, int32(this.mViewMode)) 142 | 143 | # Get the viewMode property 144 | proc viewMode*(this: Calendar): ViewMode = this.mViewMode 145 | 146 | # Get the oldViewMode property 147 | proc oldViewMode*(this: Calendar): ViewMode = this.mOldView 148 | 149 | proc `showWeekNumber=`*(this: Calendar, value: bool) = this.mShowWeekNum = value 150 | proc showWeekNumber*(this: Calendar): bool = this.mShowWeekNum 151 | 152 | proc `noTodayCircle=`*(this: Calendar, value: bool) = this.mNoTodayCircle = value 153 | proc noTodayCircle*(this: Calendar): bool = this.mNoTodayCircle 154 | 155 | proc `noToday=`*(this: Calendar, value: bool) = this.mNoToday = value 156 | proc noToday*(this: Calendar): bool = this.mNoToday 157 | 158 | proc `noTrailingDates=`*(this: Calendar, value: bool) = this.mNoTrailDates = value 159 | proc noTrailingDates*(this: Calendar): bool = this.mNoTrailDates 160 | 161 | proc `shortDateNames=`*(this: Calendar, value: bool) = this.mShortDateNames = value 162 | proc shortDateNames*(this: Calendar): bool = this.mShortDateNames 163 | 164 | 165 | proc calWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 166 | 167 | case msg 168 | of WM_DESTROY: 169 | RemoveWindowSubclass(hw, calWndProc, scID) 170 | var this = cast[Calendar](refData) 171 | this.destructor() 172 | 173 | 174 | of WM_LBUTTONDOWN: 175 | var this = cast[Calendar](refData) 176 | this.leftButtonDownHandler(msg, wpm, lpm) 177 | 178 | of WM_LBUTTONUP: 179 | var this = cast[Calendar](refData) 180 | this.leftButtonUpHandler(msg, wpm, lpm) 181 | 182 | of WM_RBUTTONDOWN: 183 | var this = cast[Calendar](refData) 184 | this.rightButtonDownHandler(msg, wpm, lpm) 185 | 186 | of WM_RBUTTONUP: 187 | var this = cast[Calendar](refData) 188 | this.rightButtonUpHandler(msg, wpm, lpm) 189 | 190 | of WM_MOUSEMOVE: 191 | var this = cast[Calendar](refData) 192 | this.mouseMoveHandler(msg, wpm, lpm) 193 | 194 | of WM_MOUSELEAVE: 195 | var this = cast[Calendar](refData) 196 | this.mouseLeaveHandler() 197 | 198 | of WM_CONTEXTMENU: 199 | var this = cast[Calendar](refData) 200 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 201 | 202 | of MM_NOTIFY_REFLECT: 203 | var this = cast[Calendar](refData) 204 | let nmh = cast[LPNMHDR](lpm) 205 | case nmh.code 206 | of MCN_SELECT: 207 | let nms = cast[LPNMSELCHANGE](lpm) 208 | this.setValueInternal(nms.stSelStart) 209 | if this.onValueChanged != nil: this.onValueChanged(this, newEventArgs()) 210 | of MCN_SELCHANGE: 211 | let nms = cast[LPNMSELCHANGE](lpm) 212 | this.setValueInternal(nms.stSelStart) 213 | if this.onSelectionCommitted != nil: this.onSelectionCommitted(this, newEventArgs()) 214 | of MCN_VIEWCHANGE: 215 | let nmv = cast[LPNMVIEWCHANGE](lpm) 216 | this.mViewMode = cast[ViewMode](nmv.dwNewView) 217 | this.mOldView = cast[ViewMode](nmv.dwOldView) 218 | if this.onViewChanged != nil: this.onViewChanged(this, newEventArgs()) 219 | else: discard 220 | return 0 221 | 222 | else: return DefSubclassProc(hw, msg, wpm, lpm) 223 | return DefSubclassProc(hw, msg, wpm, lpm) 224 | -------------------------------------------------------------------------------- /Nimforms/checkbox.nim: -------------------------------------------------------------------------------- 1 | # checkbox module Created on 29-Mar-2023 11:23 PM; Author kcvinker 2 | #[============================================CheckBox Docs====================================== 3 | Constructor - newCheckBox 4 | Functions: 5 | createHandle 6 | Properties: 7 | All props inherited from Control type 8 | checked : bool 9 | 10 | Events 11 | EventHandler type - proc(c: Control, e: EventArgs) 12 | onCheckedChanged 13 | ==========================================================================================================]# 14 | 15 | # Constants 16 | # const 17 | 18 | var cbCount = 1 19 | 20 | 21 | 22 | # Forward declaration 23 | proc cbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 24 | proc createHandle*(this: CheckBox) 25 | 26 | # CheckBox constructor 27 | proc newCheckBox*(parent: Form, text: string, x: int32 = 10, y: int32 = 10, w: int32 = 0, h: int32 = 0): CheckBox = 28 | new(result) 29 | result.mKind = ctCheckBox 30 | result.mClassName = cast[LPCWSTR](BtnClass[0].addr) 31 | result.mName = "CheckBox_" & $cbCount 32 | result.mParent = parent 33 | result.mXpos = x 34 | result.mYpos = y 35 | result.mWidth = w 36 | result.mHeight = h 37 | result.mText = text 38 | result.mWtext = newWideString(result.mText) 39 | # result.mFont = parent.mFont 40 | result.mHasFont = true 41 | result.mHasText = true 42 | result.mBackColor = parent.mBackColor 43 | result.cloneParentFont() 44 | result.mAutoSize = true 45 | result.mForeColor = CLR_BLACK 46 | result.mStyle = WS_CHILD or WS_VISIBLE or WS_TABSTOP or BS_AUTOCHECKBOX 47 | result.mExStyle = WS_EX_LTRREADING or WS_EX_LEFT 48 | result.mTextStyle = DT_SINGLELINE or DT_VCENTER 49 | cbCount += 1 50 | parent.mControls.add(result) 51 | if parent.mCreateChilds: result.createHandle() 52 | 53 | proc setCbStyle(this: CheckBox) = 54 | if this.mRightAlign: 55 | this.mStyle = this.mStyle or BS_RIGHTBUTTON 56 | this.mTextStyle = this.mTextStyle or DT_RIGHT 57 | this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 58 | 59 | 60 | # Create CheckBox's hwnd 61 | proc createHandle*(this: CheckBox) = 62 | this.setCbStyle() 63 | this.createHandleInternal() 64 | if this.mHandle != nil: 65 | this.setSubclass(cbWndProc) 66 | this.setFontInternal() 67 | this.setIdealSize() 68 | if this.mChecked: this.sendMsg(BM_SETCHECK, 1, 0) 69 | 70 | method autoCreate(this: CheckBox) = this.createHandle() 71 | 72 | # # Set the checked property 73 | proc `checked=`*(this: CheckBox, value: bool) {.inline.} = 74 | this.mChecked = value 75 | if this.mIsCreated: this.sendMsg(BM_SETCHECK, int32(value), 0) 76 | 77 | # # Get the checked property 78 | proc checked*(this: CheckBox): bool = this.mChecked 79 | 80 | 81 | proc cbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 82 | 83 | case msg 84 | of WM_DESTROY: 85 | RemoveWindowSubclass(hw, cbWndProc, scID) 86 | var this = cast[CheckBox](refData) 87 | this.destructor() 88 | 89 | of WM_LBUTTONDOWN: 90 | var this = cast[CheckBox](refData) 91 | this.leftButtonDownHandler(msg, wpm, lpm) 92 | of WM_LBUTTONUP: 93 | var this = cast[CheckBox](refData) 94 | this.leftButtonUpHandler(msg, wpm, lpm) 95 | of WM_RBUTTONDOWN: 96 | var this = cast[CheckBox](refData) 97 | this.rightButtonDownHandler(msg, wpm, lpm) 98 | of WM_RBUTTONUP: 99 | var this = cast[CheckBox](refData) 100 | this.rightButtonUpHandler(msg, wpm, lpm) 101 | of WM_MOUSEMOVE: 102 | var this = cast[CheckBox](refData) 103 | this.mouseMoveHandler(msg, wpm, lpm) 104 | of WM_MOUSELEAVE: 105 | var this = cast[CheckBox](refData) 106 | this.mouseLeaveHandler() 107 | of WM_CONTEXTMENU: 108 | var this = cast[CheckBox](refData) 109 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 110 | 111 | of MM_LABEL_COLOR: 112 | var this = cast[CheckBox](refData) 113 | let hdc = cast[HDC](wpm) 114 | SetBkColor(hdc, this.mBackColor.cref) 115 | return cast[LRESULT](this.mBkBrush) 116 | 117 | of MM_CTL_COMMAND: 118 | var this = cast[CheckBox](refData) 119 | this.mChecked = bool(this.sendMsg(BM_GETCHECK, 0, 0)) 120 | if this.onCheckedChanged != nil: this.onCheckedChanged(this, newEventArgs()) 121 | 122 | of MM_NOTIFY_REFLECT: 123 | var this = cast[CheckBox](refData) 124 | let nmcd = cast[LPNMCUSTOMDRAW](lpm) 125 | case nmcd.dwDrawStage 126 | of CDDS_PREERASE: return CDRF_NOTIFYPOSTERASE 127 | of CDDS_PREPAINT: 128 | if not this.mRightAlign: 129 | nmcd.rc.left += 18 130 | else: 131 | nmcd.rc.right -= 18 132 | if (this.mDrawMode and 1) == 1: SetTextColor(nmcd.hdc, this.mForeColor.cref) 133 | DrawTextW(nmcd.hdc, &this.mWtext, this.mWtext.wcLen, nmcd.rc.addr, this.mTextStyle) 134 | return CDRF_SKIPDEFAULT 135 | else: discard 136 | return 0 137 | 138 | else: return DefSubclassProc(hw, msg, wpm, lpm) 139 | return DefSubclassProc(hw, msg, wpm, lpm) 140 | -------------------------------------------------------------------------------- /Nimforms/colors.nim: -------------------------------------------------------------------------------- 1 | # color module. Created on 26-Mar-2023 06:56 PM 2 | # This module implements all functions related to colors 3 | 4 | proc newColor(clr: uint): Color = 5 | result.value = clr 6 | result.red = clr shr 16 7 | result.green = (clr and 0x00ff00) shr 8 8 | result.blue = clr and 0x0000ff 9 | result.cref = cast[COLORREF]((result.blue shl 16) or (result.green shl 8) or result.red) 10 | 11 | proc clip(value: auto): auto {.inline.} = clamp(value, 0, 255) 12 | 13 | proc isDark(this: Color): bool {.inline.} = 14 | let x : float = (float(this.red) * 0.2126) + 15 | (float(this.green) * 0.7152) + 16 | (float(this.blue) * 0.0722) 17 | result = x < 40 18 | 19 | proc getChangedColorRef(this: Color, adj: float): COLORREF = 20 | let red = clip(uint(float(this.red) * adj)) 21 | let green = clip(uint(float(this.green) * adj)) 22 | let blue = clip(uint(float(this.blue) * adj)) 23 | result = cast[COLORREF]((blue shl 16) or (green shl 8) or red) 24 | 25 | proc getChangedColor(this: Color, adj: float): Color = 26 | result.red = clip(uint(float(this.red) * adj)) 27 | result.green = clip(uint(float(this.green) * adj)) 28 | result.blue = clip(uint(float(this.blue) * adj)) 29 | result.cref = cast[COLORREF]((result.blue shl 16) or (result.green shl 8) or result.red) 30 | 31 | proc makeHBRUSH(this: Color): HBRUSH = CreateSolidBrush(this.cref) 32 | 33 | proc clrRefFromRGB(red, green, blue: uint): COLORREF = cast[COLORREF]((blue shl 16) or (green shl 8) or red) 34 | 35 | proc makeCREF(r, g, b: float): COLORREF = 36 | cast[COLORREF]((uint(b) shl 16) or (uint(g) shl 8) or uint(r)) 37 | 38 | 39 | proc getHotBrush(this: Color, adj: float): HBRUSH = 40 | let clrRef = this.getChangedColorRef(adj) 41 | result = CreateSolidBrush(clrRef) 42 | 43 | type 44 | FlotColor = object 45 | red, green, blue: float 46 | 47 | proc newFlotColor(clr: Color): FlotColor = 48 | result.red = float(clr.red) 49 | result.green = float(clr.green) 50 | result.blue = float(clr.blue) 51 | 52 | proc createGradientBrush( this: var GradDraw, dc: HDC, rc: RECT, dmode: GdrawMode) = 53 | var gc : GradColor 54 | var isDef : bool = true 55 | if dmode == gmDefault or dmode == gmClicked: 56 | gc = this.gcDef 57 | else: 58 | gc = this.gcHot 59 | isDef = false 60 | 61 | let isRtL: bool = false 62 | var memHDC: HDC = CreateCompatibleDC(dc) 63 | var hBmp: HBITMAP = CreateCompatibleBitmap(dc, rc.right, rc.bottom) 64 | let loopEnd: int32 = (if isRtL: rc.right else: rc.bottom) 65 | let flEnd = float(loopEnd) 66 | let c1 = newFlotColor(gc.c1) 67 | let c2 = newFlotColor(gc.c2) 68 | SelectObject(memHDC, hBmp) 69 | for i in 0.. len(biggerItem): 149 | biggerItem = item 150 | result = int32(len(biggerItem)) 151 | 152 | proc insertItemsInternal(this: ComboBox) = 153 | if this.mItems.len > 0: 154 | let nChars = this.getBiggerLength() 155 | # var wptr = cast[WArrayPtr](alloc0(bytes)) 156 | appData.sendMsgBuffer.ensureSize(nChars) 157 | for item in this.mItems: 158 | # fillWstring(wptr[0].addr, item) 159 | appData.sendMsgBuffer.updateBuffer(item) 160 | this.sendMsg(CB_ADDSTRING, 0, &appData.sendMsgBuffer) 161 | 162 | if this.mSelIndex > -1: this.sendMsg(CB_SETCURSEL, this.mSelIndex, 0) 163 | 164 | # Create ComboBox's hwnd 165 | proc createHandle*(this: ComboBox) = 166 | this.setCmbStyle() 167 | this.createHandleInternal(this.mReEnabled) 168 | if this.mHandle != nil: 169 | this.setSubclass(cmbWndProc) 170 | this.setFontInternal() 171 | this.getComboInfo() 172 | this.insertItemsInternal() 173 | this.mReEnabled = false 174 | 175 | method autoCreate(this: ComboBox) = this.createHandle() 176 | 177 | proc addItem*(this: ComboBox, item: auto) = 178 | let sitem : string = (if item is string: item else: $item) 179 | if this.mIsCreated: 180 | appData.sendMsgBuffer.updateBuffer(sitem) 181 | this.sendMsg(CB_ADDSTRING, 0, &appData.sendMsgBuffer) 182 | this.mItems.add(sitem) 183 | 184 | proc addItems*(this: ComboBox, args: varargs[string, `$`]) = 185 | for item in args: 186 | if this.mIsCreated: 187 | appData.sendMsgBuffer.updateBuffer(item) 188 | this.sendMsg(CB_ADDSTRING, 0, &appData.sendMsgBuffer) 189 | this.mItems.add(item) 190 | 191 | proc removeItem*(this: ComboBox, item: auto) = 192 | if this.mIsCreated: 193 | let sitem : string = (if item is string: item else: $item) 194 | let index = int32(this.sendMsg(CB_FINDSTRINGEXACT, -1, &appData.sendMsgBuffer)) 195 | if index != CB_ERR: 196 | this.sendMsg(CB_DELETESTRING, index, 0) 197 | this.mItems = filter(seq, proc(x: string): bool = x != sitem) 198 | 199 | proc removeItem*(this: ComboBox, index: int32) = 200 | if this.mIsCreated and index > -1: 201 | this.sendMsg(CB_DELETESTRING, index, 0) 202 | this.mItems.delete(index) 203 | 204 | proc removeAll*(this: ComboBox) = 205 | if this.mItems.len > 0: 206 | this.mItems.setLen(0) 207 | if this.mIsCreated: this.sendMsg(CB_DELETESTRING, 0, 0) 208 | 209 | 210 | # Set the hasInput property 211 | proc `hasInput=`*(this: ComboBox, value: bool) {.inline.} = 212 | if this.mHasInput != value: 213 | this.mHasInput = value 214 | if this.mIsCreated: 215 | this.mSelIndex = int32(this.sendMsg(CB_GETCURSEL, 0, 0 )) 216 | this.mReEnabled = true 217 | DestroyWindow(this.mHandle) 218 | this.createHandle() 219 | 220 | # Get the checked property 221 | proc hasInput*(this: ComboBox): bool {.inline.} = this.mHasInput 222 | 223 | proc `selectedIndex=`*(this: ComboBox, value: int32) = 224 | this.mSelIndex = value 225 | if this.mIsCreated: this.sendMsg(CB_SETCURSEL, value, 0) 226 | 227 | proc selectedIndex*(this: ComboBox): int32 = 228 | result = (if this.mIsCreated: int32(this.sendMsg(CB_GETCURSEL, 0, 0)) else: -1) 229 | 230 | proc `selctedItem=`*(this: ComboBox, value: auto) = 231 | if this.mIsCreated and this.mItems.len > 0: 232 | let sitem : string = (if value is string: value else: $value) 233 | appData.sendMsgBuffer.updateBuffer(sitem) 234 | let index = int32(this.sendMsg(CB_FINDSTRINGEXACT, -1, &appData.sendMsgBuffer)) 235 | if index != CB_ERR: this.sendMsg(CB_SETCURSEL, index, 0) 236 | 237 | proc selctedItem*(this: ComboBox): string = 238 | if this.mIsCreated: 239 | this.mSelIndex = int32(this.sendMsg(CB_GETCURSEL, 0, 0)) 240 | if this.mSelIndex != CB_ERR: 241 | let iLen = int32(this.sendMsg(CB_GETLBTEXTLEN, this.mSelIndex, 0)) 242 | appData.sendMsgBuffer.ensureSize(iLen + 1) 243 | this.sendMsg(CB_GETLBTEXT, this.mSelIndex, &appData.sendMsgBuffer) 244 | result = appData.sendMsgBuffer.toStr 245 | #end if 246 | else: 247 | result = "" 248 | 249 | proc items*(this: ComboBox) : seq[string] = this.mItems 250 | 251 | 252 | proc getComboMousePoints(): POINT = 253 | let value: DWORD = GetMessagePos() 254 | let x = int32(LOWORD(value)) 255 | let y = int32(HIWORD(value)) 256 | result = POINT(x:x, y:y) 257 | 258 | proc isMouseInCombo(hw: HWND): bool = 259 | var rc: RECT 260 | GetWindowRect(hw, rc.unsafeAddr) 261 | var pts = getComboMousePoints() 262 | result = bool(PtInRect(rc.unsafeAddr, pts)) 263 | 264 | proc mouseLeaveHandler(this: ComboBox): LRESULT = 265 | if this.mHasInput: 266 | if isMouseInCombo(this.mHandle): 267 | return 1 268 | else: 269 | if this.onMouseLeave != nil: this.onMouseLeave(this, newEventArgs()) 270 | else: 271 | if this.onMouseLeave != nil: this.onMouseLeave(this, newEventArgs()) 272 | return 0 273 | 274 | 275 | proc cmbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 276 | case msg 277 | of WM_DESTROY: 278 | RemoveWindowSubclass(hw, cmbWndProc, scID) 279 | var this = cast[ComboBox](refData) 280 | this.destructor() 281 | 282 | of WM_LBUTTONDOWN: 283 | var this = cast[ComboBox](refData) 284 | this.leftButtonDownHandler(msg, wpm, lpm) 285 | 286 | of WM_LBUTTONUP: 287 | var this = cast[ComboBox](refData) 288 | this.leftButtonUpHandler(msg, wpm, lpm) 289 | 290 | of WM_RBUTTONDOWN: 291 | var this = cast[ComboBox](refData) 292 | this.rightButtonDownHandler(msg, wpm, lpm) 293 | 294 | of WM_RBUTTONUP: 295 | var this = cast[ComboBox](refData) 296 | this.rightButtonUpHandler(msg, wpm, lpm) 297 | 298 | of WM_MOUSEMOVE: 299 | var this = cast[ComboBox](refData) 300 | this.mouseMoveHandler(msg, wpm, lpm) 301 | 302 | of WM_MOUSELEAVE: 303 | var this = cast[ComboBox](refData) 304 | return this.mouseLeaveHandler() 305 | 306 | of WM_KEYDOWN: 307 | var this = cast[ComboBox](refData) 308 | this.keyDownHandler(wpm) 309 | 310 | of WM_KEYUP: 311 | var this = cast[ComboBox](refData) 312 | this.keyUpHandler(wpm) 313 | 314 | of WM_CHAR: 315 | var this = cast[ComboBox](refData) 316 | this.keyPressHandler(wpm) 317 | 318 | of WM_CONTEXTMENU: 319 | var this = cast[ComboBox](refData) 320 | if this.mContextMenu != nil: 321 | this.mContextMenu.showMenu(lpm) 322 | 323 | of MM_CTL_COMMAND: 324 | var this = cast[ComboBox](refData) 325 | case HIWORD(wpm) 326 | of CBN_SELCHANGE: 327 | if this.onSelectionChanged != nil: 328 | this.onSelectionChanged(this, newEventArgs()) 329 | of CBN_EDITCHANGE: 330 | if this.onTextChanged != nil: 331 | this.onTextChanged(this, newEventArgs()) 332 | of CBN_EDITUPDATE: 333 | if this.onTextUpdated != nil: 334 | this.onTextUpdated(this, newEventArgs()) 335 | of CBN_DROPDOWN: 336 | if this.onListOpened != nil: 337 | this.onListOpened(this, newEventArgs()) 338 | of CBN_CLOSEUP: 339 | if this.onListClosed != nil: 340 | this.onListClosed(this, newEventArgs()) 341 | of CBN_SELENDOK: 342 | if this.onSelectionCommitted != nil: 343 | this.onSelectionCommitted(this, newEventArgs()) 344 | of CBN_SELENDCANCEL: 345 | if this.onSelectionCancelled != nil: 346 | this.onSelectionCancelled(this, newEventArgs()) 347 | else: 348 | discard 349 | 350 | of MM_LABEL_COLOR: 351 | var this = cast[ComboBox](refData) 352 | if this.mDrawMode > 0: 353 | let hdc = cast[HDC](wpm) 354 | if (this.mDrawMode and 1) == 1: 355 | SetTextColor(hdc, this.mForeColor.cref) 356 | if (this.mDrawMode and 2) == 2: 357 | SetBkColor(hdc, this.mBackColor.cref) 358 | return cast[LRESULT](this.mBkBrush) 359 | 360 | else: 361 | return DefSubclassProc(hw, msg, wpm, lpm) 362 | return DefSubclassProc(hw, msg, wpm, lpm) 363 | 364 | 365 | 366 | 367 | proc cmbEditWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 368 | var this = cast[ComboBox](refData) 369 | case msg 370 | of WM_DESTROY: 371 | var this = cast[ComboBox](refData) 372 | RemoveWindowSubclass(hw, cmbEditWndProc, scID) 373 | of WM_LBUTTONDOWN: 374 | var this = cast[ComboBox](refData) 375 | this.leftButtonDownHandler(msg, wpm, lpm) 376 | of WM_LBUTTONUP: 377 | var this = cast[ComboBox](refData) 378 | this.leftButtonUpHandler(msg, wpm, lpm) 379 | of WM_RBUTTONDOWN: 380 | var this = cast[ComboBox](refData) 381 | this.rightButtonDownHandler(msg, wpm, lpm) 382 | of WM_RBUTTONUP: 383 | var this = cast[ComboBox](refData) 384 | this.rightButtonUpHandler(msg, wpm, lpm) 385 | of WM_MOUSEMOVE: 386 | var this = cast[ComboBox](refData) 387 | this.mouseMoveHandler(msg, wpm, lpm) 388 | of WM_MOUSELEAVE: 389 | var this = cast[ComboBox](refData) 390 | return this.mouseLeaveHandler() 391 | of WM_KEYDOWN: 392 | var this = cast[ComboBox](refData) 393 | if this.hasInput: 394 | this.keyDownHandler(wpm) 395 | of WM_KEYUP: 396 | var this = cast[ComboBox](refData) 397 | if this.hasInput: 398 | this.keyUpHandler(wpm) 399 | of WM_CHAR: 400 | var this = cast[ComboBox](refData) 401 | if this.hasInput: 402 | this.keyPressHandler(wpm) 403 | of MM_EDIT_COLOR: 404 | var this = cast[ComboBox](refData) 405 | if this.mDrawMode > 0: 406 | let hdc = cast[HDC](wpm) 407 | if (this.mDrawMode and 1) == 1: 408 | SetTextColor(hdc, this.mForeColor.cref) 409 | if (this.mDrawMode and 2) == 2: 410 | SetBkColor(hdc, this.mBackColor.cref) 411 | return cast[LRESULT](this.mBkBrush) 412 | 413 | 414 | else: return DefSubclassProc(hw, msg, wpm, lpm) 415 | return DefSubclassProc(hw, msg, wpm, lpm) 416 | -------------------------------------------------------------------------------- /Nimforms/commons.nim: -------------------------------------------------------------------------------- 1 | # commons module - Created on 26-Mar-2023 07:05 PM 2 | # This module implements common functions & declarations. 3 | 4 | # Window style combinations 5 | # import macros 6 | # import std/tables 7 | let 8 | fixedSingleExStyle : DWORD = WS_EX_LEFT or WS_EX_LTRREADING or WS_EX_RIGHTSCROLLBAR or WS_EX_WINDOWEDGE or WS_EX_CONTROLPARENT or WS_EX_APPWINDOW 9 | fixedSingleStyle : DWORD = WS_OVERLAPPED or WS_TABSTOP or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_GROUP or WS_SYSMENU or WS_DLGFRAME or WS_BORDER or WS_CAPTION or WS_CLIPCHILDREN or WS_CLIPSIBLINGS 10 | fixed3DExStyle : DWORD = WS_EX_LEFT or WS_EX_LTRREADING or WS_EX_RIGHTSCROLLBAR or WS_EX_WINDOWEDGE or WS_EX_CLIENTEDGE or WS_EX_CONTROLPARENT or WS_EX_APPWINDOW or WS_EX_OVERLAPPEDWINDOW 11 | fixed3DStyle : DWORD = WS_OVERLAPPED or WS_TABSTOP or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_GROUP or WS_SYSMENU or WS_DLGFRAME or WS_BORDER or WS_CAPTION or WS_CLIPCHILDREN or WS_CLIPSIBLINGS 12 | fixedDialogExStyle : DWORD = WS_EX_LEFT or WS_EX_LTRREADING or WS_EX_RIGHTSCROLLBAR or WS_EX_DLGMODALFRAME or WS_EX_WINDOWEDGE or WS_EX_CONTROLPARENT or WS_EX_APPWINDOW 13 | fixedDialogStyle : DWORD = WS_OVERLAPPED or WS_TABSTOP or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_GROUP or WS_SYSMENU or WS_DLGFRAME or WS_BORDER or WS_CAPTION or WS_CLIPCHILDREN or WS_CLIPSIBLINGS 14 | normalWinExStyle : DWORD = WS_EX_LEFT or WS_EX_LTRREADING or WS_EX_RIGHTSCROLLBAR or WS_EX_WINDOWEDGE or WS_EX_CONTROLPARENT or WS_EX_APPWINDOW 15 | normalWinStyle : DWORD = WS_OVERLAPPEDWINDOW or WS_TABSTOP or WS_CLIPCHILDREN or WS_CLIPSIBLINGS 16 | fixedToolExStyle : DWORD = WS_EX_LEFT or WS_EX_LTRREADING or WS_EX_RIGHTSCROLLBAR or WS_EX_TOOLWINDOW or WS_EX_WINDOWEDGE or WS_EX_CONTROLPARENT or WS_EX_APPWINDOW 17 | fixedToolStyle : DWORD = WS_OVERLAPPED or WS_TABSTOP or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_GROUP or WS_SYSMENU or WS_DLGFRAME or WS_BORDER or WS_CAPTION or WS_CLIPCHILDREN or WS_CLIPSIBLINGS 18 | sizableToolExStyle : DWORD = WS_EX_LEFT or WS_EX_LTRREADING or WS_EX_RIGHTSCROLLBAR or WS_EX_TOOLWINDOW or WS_EX_WINDOWEDGE or WS_EX_CONTROLPARENT or WS_EX_APPWINDOW 19 | sizableToolStyle : DWORD = WS_OVERLAPPED or WS_TABSTOP or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_GROUP or WS_THICKFRAME or WS_SYSMENU or WS_DLGFRAME or WS_BORDER or WS_CAPTION or WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN or WS_CLIPSIBLINGS 20 | 21 | const # These 4 constants are used by ListView 22 | HDM_FIRST = 0x1200 23 | HDM_LAYOUT = HDM_FIRST+5 24 | HDM_HITTEST = HDM_FIRST+6 25 | HDM_GETITEMRECT = HDM_FIRST+7 26 | GWLP_USERDATA = -21 27 | 28 | 29 | let CLR_WHITE = newColor(0xFFFFFF) 30 | let CLR_BLACK = newColor(0x000000) 31 | 32 | proc u16_to_i16(value: uint16): int16 = cast[int16]((value and 0xFFFF)) 33 | proc adjDpi(x: int32) : int32 {.inline.} = int32(float(x) * appData.scaleF) 34 | 35 | # Some control needs to extract mouse position from lparam value. 36 | proc getMousePos(pt: ptr POINT, lpm: LPARAM) = 37 | if lpm == 0: 38 | GetCursorPos(pt) 39 | else: 40 | # echo "hwd lpm ", HIWORD(lpm), ", lpm ", lpm 41 | pt.x = int32(u16_to_i16(LOWORD(lpm))) 42 | pt.y = int32(u16_to_i16(HIWORD(lpm))) 43 | 44 | proc getMousePos(lpm: LPARAM): POINT = 45 | result = POINT( x: int32(u16_to_i16(LOWORD(lpm))), 46 | y: int32(u16_to_i16(HIWORD(lpm))) 47 | ) 48 | 49 | 50 | proc getMousePosOnMsg(): POINT = 51 | let dw_value = GetMessagePos() 52 | result.x = LONG(LOWORD(dw_value)) 53 | result.y = LONG(HIWORD(dw_value)) 54 | 55 | # template strLiteralToWChrPtr(s: string): LPCWSTR = 56 | # cast[LPCWSTR](cast[ptr UncheckedArray[byte]](s[0].addr).toOpenArray(0, s.len).map(a => a.uint16)[0].addr) 57 | 58 | proc registerMessageWindowClass(clsName: LPCWSTR, pFunc: WNDPROC) = 59 | var wc : WNDCLASSEXW 60 | wc.cbSize = cast[UINT](sizeof(wc)) 61 | wc.lpfnWndProc = pFunc 62 | wc.hInstance = appData.hInstance 63 | wc.lpszClassName = clsName 64 | RegisterClassExW(wc.addr) 65 | 66 | 67 | #==========MENU FILE INCLUDE============================================== 68 | include menu 69 | #========================================================================= 70 | 71 | proc getWidthOfString(value: string, ctlHwnd: HWND, fontHwnd: HFONT): int = 72 | var 73 | txtlen: int = value.len 74 | ss : SIZE 75 | hdc: HDC = GetDC(ctlHwnd) 76 | SelectObject(hdc, cast[HGDIOBJ](fontHwnd)) 77 | GetTextExtentPoint32(hdc, toWcharPtr(value), int32(txtlen), ss.unsafeAddr) 78 | ReleaseDC(ctlHwnd, hdc) 79 | result = ss.cx 80 | 81 | proc getWidthOfColumnNames(lv: ListView, names: varargs[string, `$`]) : OrderedTable[string, int32] = 82 | var 83 | txtlen: int 84 | ss : SIZE 85 | hdc: HDC = GetDC(lv.mHandle) 86 | res : OrderedTable[string, int32] 87 | SelectObject(hdc, cast[HGDIOBJ](lv.mFont.handle)) 88 | for value in names: 89 | # echo value 90 | txtlen = value.len 91 | GetTextExtentPoint32(hdc, toWcharPtr(value), int32(txtlen), ss.unsafeAddr) 92 | res[value] = int32(ss.cx + 20) 93 | 94 | ReleaseDC(lv.mHandle, hdc) 95 | return res 96 | 97 | proc getAccumulatedColWidth(lv: ListView): int32 = 98 | for col in lv.mColumns: 99 | result += col.mWidth 100 | result += 20 101 | 102 | proc sendThreadMsg(hwnd: HWND, wpm: WPARAM, lpm: LPARAM) = 103 | SendNotifyMessageW(hwnd, MM_THREAD_MSG, wpm, lpm) 104 | 105 | 106 | # Connects an event handler to a function. 107 | # usage : proce sample(c: Control, e: EventHandler) {.handles(btn.onClick).} = 108 | macro handles*(evt: untyped, fn: untyped): untyped = 109 | # Creates the function pointer assignment code (btn.onClick = funcName) 110 | let func_assign = newStmtList(newAssignment(newDotExpr(evt[0], evt[1]), fn[0])) 111 | 112 | # Now, creates a new statement. First the function and after that our newly created assignment statement. 113 | let new_code = newStmtList(fn, func_assign) 114 | result = new_code 115 | 116 | # macro name(arguments): return type = 117 | 118 | type 119 | Font1* = object 120 | name*: string 121 | size*: int32 122 | handle: HFONT 123 | 124 | 125 | # proc `=copy`(dst: var Font1, src: Font1) = 126 | # dst.name = src.name 127 | # dst.size = src.size 128 | # # dst.weight = src.weight 129 | # dst.italics = src.italics 130 | # dst.underLine = src.underLine 131 | # dst.strikeOut = src.strikeOut 132 | # if src.handle != nil : dst.createHandle() -------------------------------------------------------------------------------- /Nimforms/contextmenu.nim: -------------------------------------------------------------------------------- 1 | 2 | # contextmenu module - Created on 29-Apr-2023 16:45 3 | # This module is included at the end of controls.nim 4 | #[====================================ContextMenu Docs========================================= 5 | 6 | ContextMenu is inheriting from 'MenuBase', an Abstract type. 7 | Constructor - newContextMenu 8 | Functions: 9 | addMenuItem 10 | 11 | Properties: 12 | menus : Table[string, MenuItem] (Getter only) 13 | 14 | Events 15 | EventHandler type - proc(c: Control, e: EventArgs) 16 | 17 | 18 | ===============================================================================================]# 19 | 20 | # const 21 | # TPM_LEFTBUTTON = 0x0000 22 | # TPM_RIGHTBUTTON = 0x0002 23 | # 24 | # Class name - "Cmenu_Msg_Win" 25 | let cmenuClsName : array[14, uint16] = [0x43, 0x6D, 0x65, 0x6E, 0x75, 0x5F, 0x4D, 0x73, 0x67, 0x5F, 0x57, 0x69, 0x6E, 0] 26 | var cmenuMsgWinCreated : bool 27 | let cmnMsgWinClass = cast[LPCWSTR](cmenuClsName[0].addr) 28 | let TPM_RETURNCMD : uint32 = 0x100 29 | 30 | 31 | 32 | proc createMsgWindow(this: ContextMenu) # Forward declaration 33 | proc getMenuItem(this: ContextMenu, idNum: uint32): MenuItem # Forward declaration 34 | 35 | proc init(t: typedesc[ContextMenu]): ContextMenu = 36 | new(result) 37 | result.mHandle = CreatePopupMenu() 38 | result.mWidth = 120 39 | result.mHeight = 25 40 | result.mRightClick = true 41 | result.mDefBgBrush = newColor(0xe9ecef).makeHBRUSH() 42 | result.mHotBgBrush = newColor(0x90e0ef).makeHBRUSH() 43 | result.mBorderBrush = newColor(0x0077b6).makeHBRUSH() 44 | result.mGrayBrush = newColor(0xced4da).makeHBRUSH() 45 | result.mGrayCref = newColor(0x979dac).cref 46 | 47 | proc newContextMenu*(parent: Control, menuNames: varargs[string, `$`]): ContextMenu = 48 | result = ContextMenu.init() 49 | result.mParent = parent 50 | result.mFont = parent.mFont 51 | result.createMsgWindow() 52 | if len(menuNames) > 0: 53 | for name in menuNames: 54 | let mtyp = if name == "|": mtContextSep else: mtContextMenu 55 | var mi = newMenuItem(name, mtyp, result.mHandle, result.mMenuCount) 56 | result.mMenuCount += 1 57 | result.mMenus[name] = mi 58 | # if mtyp == mtContextMenu: 59 | # mi.insertMenuInternal(result.mHandle) 60 | # result.mMenus[name] = mi 61 | # elif mtyp == mtSeparator: 62 | # AppendMenuW(result.mHandle, MF_SEPARATOR, 0, nil) 63 | 64 | 65 | proc newContextMenu*(parent: TrayIcon, menuNames: varargs[string, `$`]): ContextMenu = 66 | result = ContextMenu.init() 67 | result.mTray = parent 68 | result.mTrayParent = true 69 | result.mFont = newFont("Tahoma", 11, autoc = true) 70 | 71 | if len(menuNames) > 0: 72 | for name in menuNames: 73 | let mtyp = if name == "|": mtContextSep else: mtContextMenu 74 | var mi = newMenuItem(name, mtyp, result.mHandle, result.mMenuCount) 75 | result.mMenuCount += 1 76 | result.mMenus[name] = mi 77 | 78 | 79 | proc cmenuDtor(this: ContextMenu) = 80 | DeleteObject(this.mHotBgBrush) 81 | DeleteObject(this.mBorderBrush) 82 | DeleteObject(this.mGrayBrush) 83 | DeleteObject(this.mDefBgBrush) 84 | if len(this.mMenus) > 0: 85 | for key, menu in this.mMenus: menu.menuItemDtor() 86 | 87 | DestroyMenu(this.mHandle) 88 | this.mFont.finalize() 89 | # echo "Context menu destroy worked" 90 | 91 | 92 | proc addMenuItem*(this: ContextMenu, parenttext: string, menutext: string): MenuItem {.discardable.} = 93 | var parent : MenuItem = this.mMenus[parenttext] 94 | parent.mHandle = CreatePopupMenu() 95 | parent.mPopup = true 96 | let mtyp = (if menutext == "|": mtContextSep else: mtContextMenu) 97 | result = newMenuItem(menutext, mtyp, parent.mHandle, parent.mMenuCount) 98 | parent.mMenuCount += 1 99 | parent.mMenus[menutext] = result 100 | 101 | 102 | # This proc will get called right before context menu showed 103 | proc cmenuInsertItem(this: MenuItem) = 104 | if len(this.mMenus) > 0: 105 | for key, menu in this.mMenus: 106 | menu.cmenuInsertItem() 107 | # echo "menu: ", this.mText, ", type: ", this.mType 108 | if this.mType == mtContextMenu: 109 | this.insertMenuInternal(this.mParentHandle) 110 | elif this.mType == mtContextSep: 111 | AppendMenuW(this.mParentHandle, MF_SEPARATOR, 0, nil) 112 | 113 | 114 | # This proc will get called right before context menu showed 115 | proc cmenuCreateHandle(this: ContextMenu) = 116 | if len(this.mMenus) > 0: 117 | for key, menu in this.mMenus: 118 | menu.cmenuInsertItem() 119 | #} 120 | #} 121 | this.mMenuInserted = true 122 | 123 | 124 | proc showMenu(this: ContextMenu, lpm: LPARAM) = 125 | # Create the message-only window and close it when proc exits. 126 | this.createMsgWindow() 127 | defer: DestroyWindow(this.mDummyHwnd) 128 | if not this.mMenuInserted: this.cmenuCreateHandle() 129 | 130 | var pt : POINT 131 | getMousePos(&pt, lpm) 132 | 133 | # ContextMenu message sometimes generated by keybord shortcuts. 134 | # In such caes points must be -1. So we need to find the mouse position. 135 | if pt.x == -1 or pt.y == -1: pt = getMousePosOnMsg() 136 | 137 | # If tray icon is going to show context menu, we need to activate 138 | # the tray icon's hidden window. Otherwise, we didn't get the keyboard focus. 139 | if this.mTray != nil: SetForegroundWindow(this.mTray.mMsgHwnd) 140 | 141 | # We are using TPM_RETURNCMD in the tpm_flag, so we don't get the 142 | # WM_COMMAND in our wndproc, we will get the selected menu id in return value. 143 | let mid = cast[uint32](TrackPopupMenu(this.mHandle, TPM_RETURNCMD, pt.x, pt.y, 0, this.mDummyHwnd, nil)) 144 | 145 | # Now, we have the menu id and we can process the menu.onClick event. 146 | if mid > 0: 147 | var menu = this.getMenuItem(mid) 148 | if menu != nil and menu.mIsEnabled: 149 | if menu.onClick != nil: menu.onClick(menu, newEventArgs()) 150 | #-------------------------------------------------------------------------- 151 | 152 | 153 | proc menus*(this: ContextMenu): Table[string, MenuItem] = return this.mMenus 154 | 155 | 156 | proc `[]`*(this: ContextMenu, key: string): MenuItem = 157 | # for k, menu in this.mMenus: 158 | # if menu.mText == key: 159 | # return menu 160 | result = this.mMenus[key] 161 | 162 | 163 | proc cmenuWndProc( hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM): LRESULT {.stdcall.} 164 | 165 | 166 | proc createMsgWindow(this: ContextMenu) = 167 | if not cmenuMsgWinCreated: 168 | registerMessageWindowClass(cmnMsgWinClass, cmenuWndProc) 169 | cmenuMsgWinCreated = true 170 | 171 | this.mDummyHwnd = CreateWindowExW(0, cmnMsgWinClass, nil, 0, 0, 0, 0, 0, 172 | HWND_MESSAGE, nil, appData.hInstance, nil) 173 | if this.mDummyHwnd != nil: 174 | SetWindowLongPtrW(this.mDummyHwnd, GWLP_USERDATA, cast[LONG_PTR](cast[PVOID](this))) 175 | 176 | 177 | proc getMenuItem(this: ContextMenu, idNum: uint32): MenuItem = 178 | for key, menu in this.mMenus: 179 | if menu.mId == idNum: return menu 180 | 181 | 182 | proc cmenuWndProc( hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM): LRESULT {.stdcall.} = 183 | case msg 184 | of WM_DESTROY: 185 | echo "Conetxt menu message-only window destroyed" 186 | 187 | of WM_MEASUREITEM: 188 | var this = cast[ContextMenu](GetWindowLongPtrW(hw, GWLP_USERDATA)) 189 | var pmi = cast[LPMEASUREITEMSTRUCT](lpm) 190 | pmi.itemWidth = UINT(this.mWidth) 191 | pmi.itemHeight = UINT(this.mHeight) 192 | return 1 193 | 194 | of WM_DRAWITEM: 195 | var this = cast[ContextMenu](GetWindowLongPtrW(hw, GWLP_USERDATA)) 196 | var dis = cast[LPDRAWITEMSTRUCT](lpm) 197 | var mi = cast[MenuItem](cast[PVOID](dis.itemData)) 198 | var txtClrRef : COLORREF = mi.mFgColor.cref 199 | 200 | if (dis.itemState and 1) == 1: 201 | # var rc : RECT 202 | if mi.mIsEnabled: 203 | let rc = RECT(left: dis.rcItem.left + 4, top: dis.rcItem.top + 2, 204 | right: dis.rcItem.right, bottom: dis.rcItem.bottom - 2) 205 | FillRect(dis.hDC, rc.unsafeAddr, this.mHotBgBrush) 206 | FrameRect(dis.hDC, rc.unsafeAddr, this.mBorderBrush) 207 | txtClrRef = 0x00000000 208 | else: 209 | FillRect(dis.hDC, dis.rcItem.unsafeAddr, this.mGrayBrush) 210 | txtClrRef = this.mGrayCref 211 | else: 212 | FillRect(dis.hDC, dis.rcItem.unsafeAddr, this.mDefBgBrush) 213 | if not mi.mIsEnabled: txtClrRef = this.mGrayCref 214 | 215 | SetBkMode(dis.hDC, 1) 216 | dis.rcItem.left += 25 217 | SelectObject(dis.hDC, this.mFont.handle) 218 | SetTextColor(dis.hDC, txtClrRef) 219 | DrawTextW(dis.hDC, mi.mWideText, -1, dis.rcItem.unsafeAddr, DT_LEFT or DT_SINGLELINE or DT_VCENTER) 220 | return 0 221 | 222 | of WM_ENTERMENULOOP: 223 | var this = cast[ContextMenu](GetWindowLongPtrW(hw, GWLP_USERDATA)) 224 | if this.onMenuShown != nil: this.onMenuShown(this.mParent, newEventArgs()) 225 | 226 | of WM_EXITMENULOOP: 227 | var this = cast[ContextMenu](GetWindowLongPtrW(hw, GWLP_USERDATA)) 228 | if this.onMenuClose != nil: this.onMenuClose(this.mParent, newEventArgs()) 229 | 230 | of WM_MENUSELECT: 231 | var this = cast[ContextMenu](GetWindowLongPtrW(hw, GWLP_USERDATA)) 232 | let idNum = uint32(LOWORD(wpm)) 233 | let hMenu = cast[HMENU](lpm) 234 | if hMenu != nil and idNum > 0: 235 | var menu = this.getMenuItem(idNum) 236 | if menu != nil and menu.mIsEnabled: 237 | if menu.onFocus != nil: menu.onFocus(menu, newEventArgs()) 238 | 239 | # of WM_COMMAND: 240 | # var this = cast[ContextMenu](GetWindowLongPtrW(hw, GWLP_USERDATA)) 241 | # let idNum = uint32(LOWORD(wpm)) 242 | # if idNum > 0: 243 | # var menu = this.getMenuItem(idNum) 244 | # if menu != nil and menu.mIsEnabled: 245 | # if menu.onClick != nil: menu.onClick(menu, newEventArgs()) 246 | 247 | else: return DefWindowProcW(hw, msg, wpm, lpm) 248 | return DefWindowProcW(hw, msg, wpm, lpm) 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /Nimforms/controls.nim: -------------------------------------------------------------------------------- 1 | # Controls module. Created on 27-Mar-2023 01:35 AM; Author kcvinker 2 | #[ 3 | Control type - Base type for all other controls and Form. 4 | Constructor - No constructor available. This is an astract type. 5 | 6 | Properties - Getter & Setter available 7 | Name Type 8 | font Font 9 | text string 10 | width int32 11 | height int32 12 | xpos int32 13 | ypos int32 14 | backColor Color 15 | foreColor Color 16 | 17 | Functions: 18 | 19 | 20 | Events 21 | EventHandler - proc(c: Control, e: EventArgs) 22 | onMouseEnter, onClick, onMouseLeave, onRightClick, onDoubleClick, 23 | onLostFocus, onGotFocus 24 | 25 | MouseEventHandler - - proc(c: Control, e: MouseEventArgs) 26 | onMouseWheel, onMouseHover, onMouseMove, onMouseDown, onMouseUp 27 | onRightMouseDown, onRightMouseUp 28 | 29 | KeyEventHandler - proc(c: Control, e: KeyEventArgs) 30 | onKeyDown, onKeyUp 31 | 32 | KeyPressEventHandler - proc(c: Control, e: KeyPressEventArgs) 33 | onKeyPress 34 | ====================================================================================================]# 35 | 36 | const 37 | BCM_FIRST = 0x1600 38 | BCM_GETIDEALSIZE = BCM_FIRST+0x1 39 | 40 | ES_NUMBER = 0x2000 41 | ES_LEFT = 0 42 | ES_CENTER = 1 43 | ES_RIGHT = 2 44 | EN_UPDATE = 0x0400 45 | EM_SETSEL = 0x00B1 46 | 47 | # Control class names 48 | let BtnClass : array[7, uint16] = [0x42, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0] 49 | 50 | # Package variables================================================== 51 | var globalCtlID : int32 = 100 52 | var globalSubClassID : UINT_PTR = 1000 53 | 54 | #===================Forward Declarations=================================== 55 | proc getMappedRect(this: Control): RECT 56 | proc setFontInternal(this: Control) {.inline.} 57 | proc cmenuDtor(this: ContextMenu) 58 | proc ctlSetPos(this: Control) {.inline.}= 59 | SetWindowPos(this.mHandle, nil, this.mXpos, this.mYpos, this.mWidth, this.mHeight, SWP_NOZORDER) 60 | 61 | # Control class's methods==================================================== 62 | proc cloneParentFont*(this: Control) = 63 | this.mFont.name = this.mParent.mFont.name 64 | this.mFont.size = this.mParent.mFont.size 65 | this.mFont.weight = this.mParent.mFont.weight 66 | this.mFont.italics = this.mParent.mFont.italics 67 | this.mFont.underLine = this.mParent.mFont.underLine 68 | this.mFont.strikeOut = this.mParent.mFont.strikeOut 69 | this.mFont.cloneParentFontHandle(nil) 70 | 71 | 72 | 73 | 74 | proc right*(this: Control, value: int32): int32 = this.getMappedRect().right + value 75 | proc bottom*(this: Control, value: int32): int32 = this.getMappedRect().bottom + value 76 | 77 | proc `->`*(this: Control, value: int32) : int32 = this.getMappedRect().right + value 78 | proc `>>`*(this: Control, value: int32): int32 = this.getMappedRect().bottom + value 79 | 80 | # Control class's properties========================================== 81 | proc handle*(this: Control): HWND = this.mHandle 82 | 83 | proc `font=`*(this: Control, value: Font) {.inline.} = 84 | this.mFont = value 85 | if this.mIsCreated: this.setFontInternal() 86 | 87 | proc font*(this: Control): Font {.inline.} = return this.mFont 88 | 89 | proc `text=`*(this: Control, value: string) {.inline.} = 90 | this.mText = value 91 | if this.mIsCreated: 92 | SetWindowTextW(this.mHandle, this.mText.toWcharPtr); 93 | 94 | proc text*(this: Control): string {.inline.} = return this.mText 95 | 96 | proc `width=`*(this: Control, value: int32) {.inline.} = 97 | this.mWidth = value 98 | if this.mIsCreated: this.ctlSetPos() 99 | # SetWindowPos(this.mHandle, nil, this.mXpos, this.mYpos, this.mWidth, this.mHeight, SWP_NOZORDER) 100 | 101 | proc width*(this: Control): int32 {.inline.} = return this.mWidth 102 | 103 | proc `height=`*(this: Control, value: int32) {.inline.} = 104 | this.mHeight = value 105 | if this.mIsCreated: discard 106 | 107 | proc height*(this: Control): int32 {.inline.} = return this.mHeight 108 | 109 | proc `xpos=`*(this: Control, value: int32) {.inline.} = 110 | this.mXpos = value 111 | if this.mIsCreated: discard 112 | 113 | proc xpos*(this: Control): int32 {.inline.} = return this.mXpos 114 | 115 | proc `ypos=`*(this: Control, value: int32) {.inline.} = 116 | this.mYpos = value 117 | if this.mIsCreated: discard 118 | 119 | proc ypos*(this: Control): int32 {.inline.} = return this.mYpos 120 | 121 | proc `backColor=`*(this: Control, value: uint) {.inline.} = 122 | this.mBackColor = newColor(value) 123 | if (this.mDrawMode and 2) != 2 : this.mDrawMode += 2 124 | if this.mIsCreated: 125 | this.mBkBrush = this.mBackColor.makeHBRUSH 126 | InvalidateRect(this.mHandle, nil, 0) 127 | 128 | proc `backColor=`*(this: Control, value: Color) {.inline.} = 129 | this.mBackColor = value 130 | if (this.mDrawMode and 2) != 2 : this.mDrawMode += 2 131 | if this.mIsCreated: 132 | this.mBkBrush = this.mBackColor.makeHBRUSH 133 | InvalidateRect(this.mHandle, nil, 0) 134 | 135 | proc backColor*(this: Control): Color {.inline.} = return this.mBackColor 136 | 137 | proc `foreColor=`*(this: Control, value: uint) {.inline.} = 138 | this.mForeColor = newColor(value) 139 | if (this.mDrawMode and 1) != 1 : this.mDrawMode += 1 140 | # this.mBkBrush = this.mForeColor.makeHBRUSH ------Delete later 141 | if this.mIsCreated: InvalidateRect(this.mHandle, nil, 0) 142 | 143 | proc foreColor*(this: Control): Color {.inline.} = return this.mForeColor 144 | 145 | # proc left*(this: Control): int32 = int32(this.mcRect.left) 146 | # proc top*(this: Control): int32 = int32(this.mcRect.top) 147 | 148 | # ============================================Private functions==================================== 149 | 150 | proc getMappedRect(this: Control): RECT = 151 | var fhwnd : HWND 152 | var rct : RECT 153 | if this.mIsCreated: 154 | GetClientRect(this.mHandle, rct.unsafeAddr) 155 | fhwnd = this.mHandle 156 | else: 157 | rct = RECT(left: this.mXpos, top: this.mYpos, right: (this.mXpos + this.mWidth), bottom: (this.mYpos + this.mHeight)) 158 | fhwnd = this.mParent.handle 159 | 160 | MapWindowPoints(fhwnd, this.mParent.handle, cast[LPPOINT](rct.unsafeAddr), 2) 161 | result = rct 162 | 163 | proc printControlRect*(this: Control) = 164 | var rct : RECT 165 | GetClientRect(this.mHandle, rct.unsafeAddr) 166 | echo "left: ", rct.left, ", top: ", rct.top, ", right: ", rct.right, ", bottom: ", rct.bottom 167 | 168 | proc mapParentPoints(this: Control) : RECT = 169 | var 170 | rc : RECT 171 | firstHwnd : HWND 172 | if this.mIsCreated: 173 | GetClientRect(this.mHandle, rc.unsafeAddr) 174 | firstHwnd = this.mHandle 175 | else: 176 | firstHwnd = this.mParent.mHandle 177 | rc = RECT(left: this.mXpos, top: this.mYpos, 178 | right: (this.mXpos + this.mWidth), bottom: (this.mYpos + this.mHeight )) 179 | 180 | MapWindowPoints(firstHwnd, this.mParent.mHandle, cast[LPPOINT](rc.unsafeAddr), 2) 181 | # echo rc.repr 182 | result = rc 183 | 184 | proc destructor(this: Control) = 185 | if this.mBkBrush != nil: DeleteObject(this.mBkBrush) 186 | if this.mCemnuUsed: this.mContextMenu.cmenuDtor() 187 | if this.mHasFont: this.mFont.finalize() 188 | if this.mHasText: this.mWtext.finalize() 189 | 190 | 191 | proc sendMsg(this: Control, msg: UINT, wpm: auto, lpm: auto): LRESULT {.discardable, inline.} = 192 | return SendMessageW(this.mHandle, msg, cast[WPARAM](wpm), cast[LPARAM](lpm)) 193 | 194 | proc setFontInternal(this: Control) {.inline.} = 195 | if this.mFont.handle == nil: this.mFont.createHandle() 196 | if this.mIsCreated: 197 | this.sendMsg(WM_SETFONT, this.mFont.handle, 1) 198 | 199 | 200 | proc setUserFont(this: Control) {.inline.} = 201 | if this.mFont.handle == nil: 202 | this.mFont.createHandle() 203 | if this.mIsCreated: 204 | this.sendMsg(WM_SETFONT, this.mFont.handle, 1) 205 | 206 | 207 | 208 | proc checkRedraw(this: Control) = 209 | if this.mIsCreated: InvalidateRect(this.mHandle, nil, 0) 210 | 211 | proc getControlText(hw: HWND): string = 212 | let count = GetWindowTextLengthW(hw) 213 | var buffer = new_wstring(count + 1) 214 | GetWindowTextW(hw, buffer[0].unsafeAddr, count + 1) 215 | result = buffer.toString 216 | 217 | proc setSubclass(this: Control, ctlWndProc: SUBCLASSPROC) = 218 | SetWindowSubclass(this.mHandle, ctlWndProc, globalSubClassID, cast[DWORD_PTR](cast[PVOID](this))) 219 | globalSubClassID += 1 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | # ===========================Event handlers for Control====================================================== 229 | proc leftButtonDownHandler(this: Control, msg: UINT, wp: WPARAM, lp: LPARAM) = 230 | if this.onMouseDown != nil: this.onMouseDown(this, newMouseEventArgs(msg, wp, lp)) 231 | 232 | proc leftButtonUpHandler(this: Control, msg: UINT, wp: WPARAM, lp: LPARAM) = 233 | if this.onMouseUp != nil: this.onMouseUp(this, newMouseEventArgs(msg, wp, lp)) 234 | if this.onClick != nil: this.onClick(this, newEventArgs()) 235 | 236 | proc rightButtonDownHandler(this: Control, msg: UINT, wp: WPARAM, lp: LPARAM) = 237 | if this.onRightMouseDown != nil: this.onRightMouseDown(this, newMouseEventArgs(msg, wp, lp)) 238 | 239 | proc rightButtonUpHandler(this: Control, msg: UINT, wp: WPARAM, lp: LPARAM) = 240 | if this.onRightMouseUp != nil: this.onRightMouseUp(this, newMouseEventArgs(msg, wp, lp)) 241 | if this.onRightClick != nil: this.onRightClick(this, newEventArgs()) 242 | 243 | proc mouseWheelHandler(this: Control, msg: UINT, wp: WPARAM, lp: LPARAM) = 244 | if this.onMouseWheel != nil: this.onMouseWheel(this, newMouseEventArgs(msg, wp, lp)) 245 | 246 | proc mouseMoveHandler(this: Control, msg: UINT, wp: WPARAM, lp: LPARAM) = 247 | if this.mIsMouseEntered: 248 | if this.onMouseMove != nil: this.onMouseMove(this, newMouseEventArgs(msg, wp, lp)) 249 | else: 250 | this.mIsMouseEntered = true 251 | if this.onMouseEnter != nil: this.onMouseEnter(this, newEventArgs()) 252 | 253 | proc mouseLeaveHandler(this: Control) = 254 | this.mIsMouseEntered = false 255 | if this.onMouseLeave != nil: this.onMouseLeave(this, newEventArgs()) 256 | 257 | proc keyDownHandler(this: Control, wp: WPARAM) = 258 | if this.onKeyDown != nil: this.onKeyDown(this, newKeyEventArgs(wp)) 259 | 260 | proc keyUpHandler(this: Control, wp: WPARAM) = 261 | if this.onKeyUp != nil: this.onKeyUp(this, newKeyEventArgs(wp)) 262 | 263 | proc keyPressHandler(this: Control, wp: WPARAM) = 264 | if this.onKeyPress != nil: this.onKeyPress(this, newKeyPressEventArgs(wp)) 265 | 266 | 267 | 268 | # Package level functions==================================================== 269 | proc setControlRect(this: Control) = 270 | var lprct = this.mcRect.unsafeAddr 271 | GetClientRect(this.mHandle, lprct); 272 | MapWindowPoints(this.mHandle, this.mParent.mHandle, cast[LPPOINT](lprct), 2); 273 | # echo this.mKind, " Set rect" 274 | 275 | proc createHandleInternal(this: Control, specialCtl: bool = false) = 276 | if not specialCtl: 277 | this.mCtlID = globalCtlID 278 | globalCtlID += 1 279 | let txtPtr : LPCWSTR = (if this.mHasText: &this.mWtext else: nil) 280 | # echo "creation started ", this.mKind 281 | this.mHandle = CreateWindowExW( this.mExStyle, 282 | this.mClassName, 283 | txtPtr, 284 | this.mStyle, this.mXpos, this.mYpos, 285 | this.mWidth, this.mHeight, 286 | this.mParent.mHandle, cast[HMENU](this.mCtlID), 287 | this.mParent.hInstance, nil) 288 | if this.mHandle != nil: 289 | # echo "creation finished ", this.mKind 290 | this.mIsCreated = true 291 | # this.setControlRect() 292 | else: 293 | echo "Error in creation of ", this.mKind, ", Err.No - ", GetLastError() 294 | 295 | # Only used CheckBox & RadioButton 296 | proc setIdealSize(this: Control) = 297 | var ss: SIZE 298 | this.sendMsg(BCM_GETIDEALSIZE, 0, ss.unsafeAddr) 299 | this.mWidth = ss.cx 300 | this.mHeight = ss.cy 301 | MoveWindow(this.mHandle, this.mXpos, this.mYpos, ss.cx, ss.cy, 1) 302 | 303 | method autoCreate(c: Control) {.base.} = 304 | quit "Childs are responsible for this" 305 | 306 | 307 | 308 | # Here we are including contextmenu module. Because, contextmenu should be available for all controls. 309 | include contextmenu 310 | 311 | # proc setContextMenuInternal(this: Control) 312 | 313 | proc contextMenu*(this: Control): ContextMenu = this.mContextMenu 314 | 315 | proc `contextMenu=`*(this: Control, value: ContextMenu) = 316 | this.mContextMenu = value 317 | this.mCemnuUsed = true 318 | 319 | proc setContextMenu*(parent: Control, menuNames: varargs[string, `$`]) : ContextMenu {.discardable.} = 320 | result = newContextMenu(parent, menuNames) 321 | parent.mContextMenu = result 322 | parent.mCemnuUsed = true -------------------------------------------------------------------------------- /Nimforms/datetimepicker.nim: -------------------------------------------------------------------------------- 1 | # datetimepicker module Created on 30-Mar-2023 12:22 PM 2 | 3 | #[=========================================DateTimePicker Docs=========================================== 4 | Constructor - newDateTimePicker 5 | Functions: 6 | createHandle 7 | Properties: 8 | All props inherited from Control type 9 | value : DateAndTime 10 | formatString : string 11 | format : DTPFormat - An enum (See typemodule.nim) 12 | rightAlign : bool 13 | noToday : bool 14 | showUpdown : bool 15 | showWeekNumber : bool 16 | noTodayCircle : bool 17 | noTrailingDates : bool 18 | shortDateNames : bool 19 | fourDigitYear : bool 20 | 21 | Events: 22 | All events inherited from Control type 23 | EventHandler - proc(c: Control, e: EventArgs) 24 | onValueChanged 25 | onCalendarOpened 26 | onCalendarClosed 27 | DateTimeEventHandler - proc(c: Control, e: DateTimeEventArgs) 28 | onTextChanged 29 | ========================================================================================================]# 30 | # Constants 31 | const 32 | DTM_FIRST = 0x1000 33 | DTN_FIRST = cast[UINT](0-740) 34 | DTN_FIRST2 = cast[UINT](0-753) 35 | DTM_GETSYSTEMTIME = DTM_FIRST+1 36 | DTM_SETSYSTEMTIME = DTM_FIRST+2 37 | DTM_GETRANGE = DTM_FIRST+3 38 | DTM_SETRANGE = DTM_FIRST+4 39 | DTM_SETFORMATA = DTM_FIRST+5 40 | DTM_SETFORMATW = DTM_FIRST+50 41 | DTM_SETMCCOLOR = DTM_FIRST+6 42 | DTM_GETMCCOLOR = DTM_FIRST+7 43 | DTM_GETMONTHCAL = DTM_FIRST+8 44 | DTM_SETMCFONT = DTM_FIRST+9 45 | DTM_GETMCFONT = DTM_FIRST+10 46 | DTM_SETMCSTYLE = DTM_FIRST+11 47 | DTM_GETMCSTYLE = DTM_FIRST+12 48 | DTM_CLOSEMONTHCAL = DTM_FIRST+13 49 | DTM_GETDATETIMEPICKERINFO = DTM_FIRST+14 50 | DTM_GETIDEALSIZE = DTM_FIRST+15 51 | DTS_UPDOWN = 0x1 52 | DTS_SHOWNONE = 0x2 53 | DTS_SHORTDATEFORMAT = 0x0 54 | DTS_LONGDATEFORMAT = 0x4 55 | DTS_SHORTDATECENTURYFORMAT = 0xc 56 | DTS_TIMEFORMAT = 0x9 57 | DTS_APPCANPARSE = 0x10 58 | DTS_RIGHTALIGN = 0x20 59 | DTN_DATETIMECHANGE = DTN_FIRST2-6 60 | DTN_USERSTRINGA = DTN_FIRST2-5 61 | DTN_USERSTRINGW = DTN_FIRST-5 62 | DTN_WMKEYDOWNA = DTN_FIRST2-4 63 | DTN_WMKEYDOWNW = DTN_FIRST-4 64 | DTN_FORMATA = DTN_FIRST2-3 65 | DTN_FORMATW = DTN_FIRST-3 66 | DTN_FORMATQUERYA = DTN_FIRST2-2 67 | DTN_FORMATQUERYW = DTN_FIRST-2 68 | DTN_DROPDOWN = DTN_FIRST2-1 69 | DTN_CLOSEUP = DTN_FIRST2 70 | 71 | var dtpCount = 1 72 | # let dtpClsName = toWcharPtr("SysDateTimePick32") 73 | let dtpClsName : array[18, uint16] = [0x53, 0x79, 0x73, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6D, 0x65, 0x50, 0x69, 0x63, 0x6B, 0x33, 0x32, 0] 74 | 75 | 76 | # Forward declaration 77 | proc dtpWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 78 | proc createHandle*(this: DateTimePicker) 79 | # DateTimePicker constructor 80 | proc newDateTimePicker*(parent: Form, x: int32 = 10, y: int32 = 10, 81 | w: int32 = 0, h: int32 = 10): DateTimePicker = 82 | new(result) 83 | result.mKind = ctDateTimePicker 84 | result.mClassName = cast[LPCWSTR](dtpClsName[0].addr) 85 | result.mName = "DateTimePicker_" & $dtpCount 86 | result.mParent = parent 87 | result.mXpos = x 88 | result.mYpos = y 89 | result.mWidth = w 90 | result.mHeight = h 91 | result.cloneParentFont() 92 | result.mHasFont = true 93 | result.mBackColor = CLR_WHITE 94 | result.mForeColor = CLR_BLACK 95 | result.mFormat = dfCustom 96 | result.mFmtStr = "dd-MM-yyyy" 97 | result.mAutoSize = true 98 | result.mStyle = WS_CHILD or WS_VISIBLE or WS_TABSTOP 99 | result.mExStyle = 0 100 | if appData.isDateInit: 101 | appData.isDateInit = true 102 | appData.iccEx.dwICC = ICC_DATE_CLASSES 103 | InitCommonControlsFunc(appData.iccEx.unsafeAddr) 104 | 105 | dtpCount += 1 106 | parent.mControls.add(result) 107 | if parent.mCreateChilds: result.createHandle() 108 | 109 | proc setDTPStyles(this: DateTimePicker) = 110 | case this.mFormat 111 | of dfCustom: this.mStyle = this.mStyle or DTS_LONGDATEFORMAT or DTS_APPCANPARSE 112 | of dfLongDate: this.mStyle = this.mStyle or DTS_LONGDATEFORMAT 113 | of dfShortDate: 114 | if this.m4DYear: 115 | this.mStyle = this.mStyle or DTS_SHORTDATECENTURYFORMAT 116 | else: 117 | this.mStyle = this.mStyle or DTS_SHORTDATEFORMAT 118 | of dfTimeOnly: this.mStyle = this.mStyle or DTS_TIMEFORMAT 119 | 120 | if this.mShowWeekNum: this.mCalStyle = this.mCalStyle or MCS_WEEKNUMBERS 121 | if this.mNoTodayCircle: this.mCalStyle = this.mCalStyle or MCS_NOTODAYCIRCLE 122 | if this.mNoToday: this.mCalStyle = this.mCalStyle or MCS_NOTODAY 123 | if this.mNoTrailDates: this.mCalStyle = this.mCalStyle or MCS_NOTRAILINGDATES 124 | if this.mShortDateNames: this.mCalStyle = this.mCalStyle or MCS_SHORTDAYSOFWEEK 125 | if this.mRightAlign: this.mStyle = this.mStyle or DTS_RIGHTALIGN 126 | if this.mShowUpdown: this.mStyle = this.mStyle or DTS_UPDOWN 127 | 128 | proc setAutoSize(this: DateTimePicker) = 129 | # Although we are using 'W' based unicode functions & messages, 130 | # here we must use ANSI message. DTM_SETFORMATW won't work here for unknown reason. 131 | if this.mFormat == dfCustom: this.sendMsg(DTM_SETFORMATA, 0, this.mFmtStr[0].unsafeAddr) 132 | if this.mCalStyle > 0: this.sendMsg(DTM_SETMCSTYLE, 0, this.mCalStyle) 133 | if this.mAutoSize: # We don't need this user set the size 134 | var ss: SIZE 135 | this.sendMsg(DTM_GETIDEALSIZE, 0, ss.unsafeAddr) 136 | this.mWidth = ss.cx + 2 137 | this.mHeight = ss.cy + 5 138 | SetWindowPos(this.mHandle, nil, this.mXpos, this.mYpos, this.mWidth, this.mHeight, SWP_NOZORDER) 139 | 140 | 141 | # Create DateTimePicker's hwnd 142 | proc createHandle*(this: DateTimePicker) = 143 | this.setDTPStyles() 144 | this.createHandleInternal() 145 | if this.mHandle != nil: 146 | this.setSubclass(dtpWndProc) 147 | this.setAutoSize() 148 | this.setFontInternal() 149 | var st: SYSTEMTIME 150 | let res = this.sendMsg(DTM_GETSYSTEMTIME, 0, st.unsafeAddr) 151 | if res == 0: this.mValue = newDateAndTime(st) 152 | 153 | method autoCreate(this: DateTimePicker) = this.createHandle() 154 | 155 | # Property section 156 | proc `value=`*(this: DateTimePicker, dateValue: DateAndTime) = # TODO 157 | this.mValue = dateValue 158 | let stime = makeSystemTime(this.mValue) 159 | if this.mIsCreated: this.sendMsg(DTM_SETSYSTEMTIME, 0, stime) 160 | 161 | proc value*(this: DateTimePicker): DateAndTime {.inline.} = this.mValue 162 | 163 | proc `formatString=`*(this: DateTimePicker, value: string) = 164 | this.mFmtStr = value 165 | this.mFormat = dfCustom 166 | if this.mIsCreated: this.sendMsg(DTM_SETFORMATA, 0, this.mFmtStr.toWcharPtr) 167 | 168 | proc `formatString=`*(this: DateTimePicker) : string {.inline.} = this.mFmtStr 169 | 170 | # If set to true, text in date time picker is right aligned. 171 | proc `rightAlign=`*(this: DateTimePicker, value: bool) {.inline.} = this.mRightAlign = value 172 | proc rightAlign*(this: DateTimePicker): bool {.inline.} = this.mRightAlign 173 | 174 | proc `format=`*(this: DateTimePicker, value: DTPFormat) {.inline.} = this.mFormat = value 175 | proc format*(this: DateTimePicker) : DTPFormat {.inline.} = this.mFormat 176 | 177 | proc `showWeekNumber=`*(this: DateTimePicker, value: bool) {.inline.} = this.mShowWeekNum = value 178 | proc showWeekNumber*(this: DateTimePicker): bool {.inline.} = this.mShowWeekNum 179 | 180 | proc `noTodayCircle=`*(this: DateTimePicker, value: bool) {.inline.} = this.mNoTodayCircle = value 181 | proc noTodayCircle*(this: DateTimePicker): bool {.inline.} = this.mNoTodayCircle 182 | 183 | proc `noToday=`*(this: DateTimePicker, value: bool) {.inline.} = this.mNoToday = value 184 | proc noToday*(this: DateTimePicker): bool {.inline.} = this.mNoToday 185 | 186 | proc `noTrailingDates=`*(this: DateTimePicker, value: bool) {.inline.} = this.mNoTrailDates = value 187 | proc noTrailingDates*(this: DateTimePicker): bool {.inline.} = this.mNoTrailDates 188 | 189 | proc `shortDateNames=`*(this: DateTimePicker, value: bool) {.inline.} = this.mShortDateNames = value 190 | proc shortDateNames*(this: DateTimePicker): bool {.inline.} = this.mShortDateNames 191 | 192 | proc `showUpdown=`*(this: DateTimePicker, value: bool) {.inline.} = this.mShowUpdown = value 193 | proc showUpdown*(this: DateTimePicker): bool {.inline.} = this.mShowUpdown 194 | 195 | proc `fourDigitYear=`*(this: DateTimePicker, value: bool) {.inline.} = this.m4DYear = value 196 | proc fourDigitYear*(this: DateTimePicker): bool {.inline.} = this.m4DYear 197 | 198 | # Overriding Control's property to resize ourself. 199 | proc `font=`*(this: DateTimePicker, value: Font) = 200 | this.mFont = value 201 | if this.mIsCreated: 202 | this.setFontInternal() 203 | this.setAutoSize() 204 | 205 | 206 | 207 | proc dtpWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 208 | # echo msg 209 | case msg 210 | of WM_DESTROY: 211 | RemoveWindowSubclass(hw, dtpWndProc, scID) 212 | var this = cast[DateTimePicker](refData) 213 | this.destructor() 214 | 215 | of WM_LBUTTONDOWN: 216 | var this = cast[DateTimePicker](refData) 217 | this.leftButtonDownHandler(msg, wpm, lpm) 218 | of WM_LBUTTONUP: 219 | var this = cast[DateTimePicker](refData) 220 | this.leftButtonUpHandler(msg, wpm, lpm) 221 | of WM_RBUTTONDOWN: 222 | var this = cast[DateTimePicker](refData) 223 | this.rightButtonDownHandler(msg, wpm, lpm) 224 | of WM_RBUTTONUP: 225 | var this = cast[DateTimePicker](refData) 226 | this.rightButtonUpHandler(msg, wpm, lpm) 227 | of WM_MOUSEMOVE: 228 | var this = cast[DateTimePicker](refData) 229 | this.mouseMoveHandler(msg, wpm, lpm) 230 | of WM_MOUSELEAVE: 231 | var this = cast[DateTimePicker](refData) 232 | this.mouseLeaveHandler() 233 | of WM_CONTEXTMENU: 234 | var this = cast[DateTimePicker](refData) 235 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 236 | 237 | of MM_NOTIFY_REFLECT: 238 | var this = cast[DateTimePicker](refData) 239 | let nm = cast[LPNMHDR](lpm) 240 | case nm.code 241 | of DTN_USERSTRINGW: 242 | if this.onTextChanged != nil: 243 | let dts = cast[LPNMDATETIMESTRINGW](lpm) 244 | var dtea = newDateTimeEventArgs(dts.pszUserString) 245 | this.onTextChanged(this, dtea) 246 | if dtea.handled: this.sendMsg(DTM_SETSYSTEMTIME, 0, dtea.mDateStruct) 247 | 248 | of DTN_DROPDOWN: 249 | if this.onCalendarOpened != nil: this.onCalendarOpened(this, newEventArgs()) 250 | 251 | of DTN_DATETIMECHANGE: 252 | if this.mDropDownCount == 0: 253 | this.mDropDownCount = 1 254 | let nmd = cast[LPNMDATETIMECHANGE](lpm) 255 | this.mValue = newDateAndTime(nmd.st) 256 | if this.onValueChanged != nil: this.onValueChanged(this, newEventArgs()) 257 | 258 | elif this.mDropDownCount == 1: 259 | this.mDropDownCount = 0 260 | return 0 261 | 262 | of DTN_CLOSEUP: 263 | if this.onCalendarClosed != nil: this.onCalendarClosed(this, newEventArgs()) 264 | else: discard 265 | return 0 266 | # of MM_LABEL_COLOR: # Message is arriving but no result 267 | # let hdc = cast[HDC](wpm) 268 | # SetTextColor(hdc, this.mForeColor.cref) 269 | # SetBkColor(hdc, this.mBackColor.cref) 270 | # return cast[LRESULT](this.mBkBrush) 271 | 272 | # of LVM_SETBKCOLOR: 273 | # echo "LVM_SETBKCOLOR ", lpm 274 | 275 | else: return DefSubclassProc(hw, msg, wpm, lpm) 276 | return DefSubclassProc(hw, msg, wpm, lpm) 277 | -------------------------------------------------------------------------------- /Nimforms/dialogs.nim: -------------------------------------------------------------------------------- 1 | # dialogs module Created on 17-May-2023 06:32 2 | #[=========================================Dialog Docs=========================================== 3 | 4 | DialogBase - Abstract type 5 | Properties: 6 | selectedPath : string 7 | nameStartPos : int 8 | extStartPos : int 9 | title : string 10 | initialFolder : string 11 | filter : string 12 | Functions: 13 | setFilter 14 | setFilters 15 | 16 | FileOpenDialog: 17 | Constructor - newFileOpenDialog 18 | Properties: 19 | All props inherited from DialogBase type 20 | multiSelect 21 | showHiddenFiles 22 | fileNames 23 | Functions: 24 | showDialog : bool 25 | 26 | FileSaveDialog: 27 | Constructor - newFileSaveDialog 28 | Properties: 29 | All props inherited from DialogBase type 30 | Functions: 31 | showDialog : bool 32 | 33 | FolderBrowserDialog: 34 | Constructor - newFolderBrowserDialog 35 | Properties: 36 | All props inherited from DialogBase type 37 | newFolderButton 38 | showFiles 39 | Functions: 40 | showDialog : bool 41 | ========================================================================================================]# 42 | 43 | const 44 | MAX_PATH = 260 45 | MAX_PATH_NEW = 32768 + 256 * 100 + 1 46 | OFN_ALLOWMULTISELECT = 0x200 47 | OFN_PATHMUSTEXIST = 0x800 48 | OFN_FILEMUSTEXIST = 0x1000 49 | OFN_FORCESHOWHIDDEN = 0x10000000 50 | OFN_OVERWRITEPROMPT = 0x2 51 | BIF_RETURNONLYFSDIRS = 0x00000001 52 | BIF_NEWDIALOGSTYLE = 0x00000040 53 | BIF_EDITBOX = 0x00000010 54 | BIF_NONEWFOLDERBUTTON = 0x00000200 55 | BIF_BROWSEINCLUDEFILES = 0x00004000 56 | OFN_EXPLORER = 0x00080000 57 | 58 | type 59 | DialogBase = ref object of RootObj 60 | mTitle, mInitDir, mFilter, mSelPath : string 61 | mFileStart, mExtStart : int 62 | mAllowAllFiles: bool 63 | 64 | FileOpenDialog* = ref object of DialogBase 65 | mMultiSel, mShowHidden : bool 66 | mSelFiles : seq[string] 67 | 68 | FileSaveDialog* = ref object of DialogBase 69 | mDefExt : string 70 | 71 | FolderBrowserDialog* = ref object of DialogBase 72 | mNewFolBtn, mShowFiles : bool 73 | 74 | 75 | 76 | proc GetOpenFileNameW(P1: LPOPENFILENAMEW): BOOL {.stdcall, dynlib: "comdlg32", importc.} 77 | proc GetSaveFileNameW(P1: LPOPENFILENAMEW): BOOL {.stdcall, dynlib: "comdlg32", importc.} 78 | proc SHBrowseForFolderW(lpbi: LPBROWSEINFOW): PIDLIST_ABSOLUTE {.stdcall, dynlib: "shell32", importc.} 79 | proc SHGetPathFromIDListW(pidl: PCIDLIST_ABSOLUTE, pszPath: LPWSTR): BOOL {.stdcall, dynlib: "shell32", importc.} 80 | proc CoTaskMemFree(pv: LPVOID): void {.stdcall, dynlib: "ole32", importc.} 81 | 82 | proc initDialogBase(this: DialogBase, ttl: string, initDir: string) = 83 | this.mTitle = ttl 84 | this.mInitDir = initDir 85 | # this.mFilter = if filter == "": "All Files" & "\0" & "*.*" & "\0" else: filter 86 | 87 | proc newFileOpenDialog*(title: string = "Open file", initDir: string = "", multisel : bool = false): FileOpenDialog = 88 | new(result) 89 | initDialogBase(result, title, initDir) 90 | # result.kind = DialogType.fileOpen 91 | result.mMultiSel = multisel 92 | 93 | proc newFileSaveDialog*(title: string = "Save As", initDir: string = ""): FileSaveDialog = 94 | new(result) 95 | initDialogBase(result, title, initDir) 96 | # result.kind = DialogType.fileSave 97 | 98 | proc newFolderBrowserDialog*(title: string = "Save As", initDir: string = ""): FolderBrowserDialog = 99 | new(result) 100 | initDialogBase(result, title, initDir) 101 | 102 | # Getter functions 103 | proc selectedPath*(this: DialogBase): string = this.mSelPath 104 | proc nameStartPos*(this: DialogBase): int = this.mFileStart 105 | proc extStartPos*(this: DialogBase): int = this.mExtStart 106 | proc title*(this: DialogBase): string = this.mTitle 107 | proc initialFolder*(this: DialogBase): string = this.mInitDir 108 | proc filter*(this: DialogBase): string = this.mFilter 109 | proc multiSelect*(this: FileOpenDialog): bool = this.mMultiSel 110 | proc showHiddenFiles*(this: FileOpenDialog): bool = this.mShowHidden 111 | proc newFolderButton*(this: FolderBrowserDialog): bool = this.mNewFolBtn 112 | proc showFiles*(this: FolderBrowserDialog): bool = this.mShowFiles 113 | proc fileNames*(this: FileOpenDialog): seq[string] = this.mSelFiles 114 | 115 | # Setter functions 116 | proc `title=`*(this: DialogBase, value: string) = this.mTitle = value 117 | proc `initialFolder=`*(this: DialogBase, value: string) = this.mInitDir = value 118 | # proc `filter=`*(this: DialogBase, value: string) = this.mFilter = value 119 | proc `multiSelect=`*(this: FileOpenDialog, value: bool) = this.mMultiSel = value 120 | proc `showHiddenFiles=`*(this: FileOpenDialog, value: bool) = this.mShowHidden = value 121 | proc `newFolderButton=`*(this: FolderBrowserDialog, value: bool) = this.mNewFolBtn = value 122 | proc `showFiles=`*(this: FolderBrowserDialog, value: bool) = this.mShowFiles = value 123 | proc `allowAllFiles=`*(this: DialogBase, value: bool) = this.mAllowAllFiles = value 124 | 125 | 126 | 127 | 128 | proc extractFileNames(this: FileOpenDialog, buff: wstring, startPos: int) = 129 | var offset : int = startPos 130 | let dirPath = toString(buff[0..startPos - 2]) # First item in buff is the directory path. 131 | for i in startPos .. MAX_PATH: 132 | let wc : WCHAR = buff[i] 133 | if ord(wc) == 0: 134 | var slice : wstring = buff[offset..i - 1] 135 | offset = i + 1 136 | this.mSelFiles.add(fmt"{dirPath}\{slice.toString}") 137 | if ord(buff[offset]) == 0: break 138 | this.mSelPath = fmt("{this.mSelFiles[0]}") 139 | 140 | 141 | 142 | 143 | proc showDialog*(this: FileOpenDialog, hwnd: HWND = nil): bool {.discardable.} = 144 | if this.mFilter.len == 0: 145 | this.mFilter = "All files\0*.*\0" 146 | else: 147 | if this.mAllowAllFiles: 148 | this.mFilter = fmt("{this.mFilter}All files\0*.*\0") 149 | var ofn: OPENFILENAMEW 150 | var buffer: wstring = new_wstring(MAX_PATH_NEW) 151 | ofn.hwndOwner = hwnd 152 | ofn.lStructSize = cast[DWORD](sizeof(ofn)) 153 | ofn.lpstrFilter = this.mFilter.toLPWSTR() 154 | ofn.lpstrFile = &buffer 155 | ofn.lpstrInitialDir = (if len(this.mInitDir) > 0: this.mInitDir.toLPWSTR() else: nil) 156 | ofn.lpstrTitle = this.mTitle.toLPWSTR() 157 | ofn.nMaxFile = MAX_PATH_NEW 158 | ofn.nMaxFileTitle = MAX_PATH 159 | ofn.Flags = OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST 160 | if this.mMultiSel: ofn.Flags = ofn.Flags or OFN_ALLOWMULTISELECT or OFN_EXPLORER 161 | if this.mShowHidden: ofn.Flags = ofn.Flags or OFN_FORCESHOWHIDDEN 162 | let ret = GetOpenFileNameW(ofn.unsafeAddr) 163 | if ret > 0: 164 | if this.mMultiSel: 165 | this.extractFileNames(buffer, cast[int](ofn.nFileOffset)) 166 | result = true 167 | else: 168 | this.mSelPath = buffer.toString 169 | result = true 170 | 171 | 172 | proc showDialog*(this: FileSaveDialog, hwnd: HWND = nil): bool = 173 | var ofn: OPENFILENAMEW 174 | var buffer: wstring = new_wstring(MAX_PATH) 175 | ofn.hwndOwner = hwnd 176 | ofn.lStructSize = cast[DWORD](sizeof(ofn)) 177 | ofn.lpstrFilter = this.mFilter.toLPWSTR() 178 | ofn.lpstrFile = &buffer 179 | ofn.lpstrInitialDir = (if len(this.mInitDir) > 0: this.mInitDir.toLPWSTR() else: nil) 180 | ofn.lpstrTitle = this.mTitle.toLPWSTR() 181 | ofn.nMaxFile = MAX_PATH 182 | ofn.nMaxFileTitle = MAX_PATH 183 | ofn.Flags = OFN_PATHMUSTEXIST or OFN_OVERWRITEPROMPT 184 | let ret = GetSaveFileNameW(ofn.unsafeAddr) 185 | if ret != 0: 186 | this.mFileStart = cast[int](ofn.nFileOffset) 187 | this.mExtStart = cast[int](ofn.nFileExtension) 188 | this.mSelPath = buffer.toString 189 | result = true 190 | 191 | 192 | proc showDialog*(this: FolderBrowserDialog, hwnd: HWND = nil): bool {.discardable.} = 193 | var buffer: wstring = new_wstring(MAX_PATH) 194 | var bi: BROWSEINFOW 195 | bi.hwndOwner = hwnd; 196 | bi.lpszTitle = this.mTitle.toLPWSTR() 197 | bi.ulFlags = BIF_RETURNONLYFSDIRS or BIF_NEWDIALOGSTYLE 198 | if this.mNewFolBtn: bi.ulFlags = bi.ulFlags or BIF_NONEWFOLDERBUTTON 199 | if this.mShowFiles: bi.ulFlags = bi.ulFlags or BIF_BROWSEINCLUDEFILES 200 | var pidl : LPITEMIDLIST = SHBrowseForFolderW(bi.unsafeAddr) 201 | if pidl != nil: 202 | if SHGetPathFromIDListW(pidl, &buffer) != 0: 203 | CoTaskMemFree(pidl) 204 | this.mSelPath = buffer.toString 205 | result = true 206 | else: 207 | CoTaskMemFree(pidl) 208 | 209 | 210 | 211 | proc setFilter*(this: DialogBase, filterName, ext: string) = 212 | if this.mFilter.len > 0: 213 | this.mFilter = fmt("{this.mFilter}{filterName}\0*{ext}\0"); 214 | else: 215 | this.mFilter = fmt("{filterName}\0*{ext}\0") 216 | 217 | 218 | proc setFilters*(this: DialogBase, description: string, extSeq: seq[string]) = 219 | # Adding multiple filters with single discription 220 | var filterSeq : seq[string] 221 | filterSeq.add(fmt("{description}\0")) 222 | let fillCount = extSeq.len - 1 223 | for i, ext in extSeq: 224 | if i == fillCount: # It's tha last extension 225 | filterSeq.add(fmt("*{ext}\0\0")) 226 | else: 227 | filterSeq.add(fmt("*{ext};")) 228 | 229 | this.mFilter = filterSeq.join("") -------------------------------------------------------------------------------- /Nimforms/events.nim: -------------------------------------------------------------------------------- 1 | 2 | # Event module - Created on 28-Mar-2023 12:47 AM 3 | const 4 | TVN_FIRST = cast[UINT](0-400) 5 | TVN_SELCHANGINGW = TVN_FIRST-50 6 | TVN_SELCHANGEDW = TVN_FIRST-51 7 | TVN_ITEMEXPANDINGW = TVN_FIRST-54 8 | TVN_ITEMEXPANDEDW = TVN_FIRST-55 9 | TVN_DELETEITEMW = TVN_FIRST-58 10 | 11 | 12 | 13 | proc newEventArgs(): EventArgs = new(result) 14 | 15 | proc getXFromLp(lp: LPARAM): int32 = cast[int32](LOWORD(lp)) 16 | proc getYFromLp(lp: LPARAM): int32 = cast[int32](HIWORD(lp)) 17 | 18 | proc newMouseEventArgs(msg: UINT, wp: WPARAM, lp: LPARAM): MouseEventArgs = 19 | new(result) 20 | let fwKeys = LOWORD(wp) 21 | result.mDelta = cast[int32](GET_WHEEL_DELTA_WPARAM(wp)) 22 | case fwKeys # IMPORTANT*********** Work here --> change 4 to 5, 8 to 9 etc 23 | of 4 : result.mShiftPressed = true 24 | of 8 : result.mCtrlPressed = true 25 | of 16 : result.mButton = mbMiddle 26 | of 32 : result.mButton = mbXButton1 27 | else: discard 28 | 29 | case msg 30 | of WM_LBUTTONDOWN, WM_LBUTTONUP: result.mButton = mbLeft 31 | of WM_RBUTTONDOWN, WM_RBUTTONUP: result.mButton = mbRight 32 | else: discard 33 | 34 | result.mx = getXFromLp(lp) 35 | result.my = getYFromLp(lp) 36 | 37 | 38 | proc newKeyEventArgs(wp: WPARAM): KeyEventArgs = 39 | new(result) 40 | result.mKeyCode = cast[Keys](wp) 41 | case result.mKeyCode 42 | of keyShift : 43 | result.mShiftPressed = true 44 | result.mModifier = keyShiftModifier 45 | of keyCtrl : 46 | result.mCtrlPressed = true 47 | result.mModifier = keyCtrlModifier 48 | of keyAlt : 49 | result.mAltPressed = true 50 | result.mModifier = keyAltModifier 51 | else : discard 52 | result.mKeyValue = cast[int32](result.mKeyCode) 53 | 54 | proc newKeyPressEventArgs(wp: WPARAM): KeyPressEventArgs = 55 | new(result) 56 | result.keyChar = cast[char](wp) 57 | 58 | proc newSizeEventArgs(msg: UINT, lp: LPARAM): SizeEventArgs = 59 | new(result) 60 | if msg == WM_SIZING: 61 | result.mWinRect = cast[LPRECT](lp) 62 | else: 63 | result.mClientArea.width = cast[int32](LOWORD(lp)) 64 | result.mClientArea.height = cast[int32](HIWORD(lp)) 65 | 66 | proc newDateTimeEventArgs(dtpStr: LPCWSTR): DateTimeEventArgs = 67 | new(result) 68 | result.mDateStr = wcharArrayToString(dtpStr) 69 | 70 | proc newTreeEventArgs(ntv: LPNMTREEVIEWW): TreeEventArgs = 71 | new(result) 72 | if ntv.hdr.code == TVN_SELCHANGINGW or ntv.hdr.code == TVN_SELCHANGEDW: 73 | case ntv.action 74 | of 0 : result.mAction = tvaUnknown 75 | of 1 : result.mAction = tvaByMouse 76 | of 2 : result.mAction = tvaByKeyboard 77 | else: discard 78 | # echo "mAction in sel change " & $result.mAction 79 | elif ntv.hdr.code == TVN_ITEMEXPANDEDW or ntv.hdr.code == TVN_ITEMEXPANDINGW: 80 | case ntv.action 81 | of 0 : result.mAction = tvaUnknown 82 | of 1 : result.mAction = tvaCollapse 83 | of 2 : result.mAction = tvaExpand 84 | else: discard 85 | # echo "mAction in expand " & $result.mAction 86 | result.mNode = cast[TreeNode](cast[PVOID](ntv.itemNew.lParam)) 87 | if ntv.itemOld.lParam > 0: 88 | result.mOldNode = cast[TreeNode](cast[PVOID](ntv.itemOld.lParam)) 89 | 90 | proc newTreeEventArgs(ntv: LPNMTVITEMCHANGE): TreeEventArgs = 91 | new(result) 92 | result.mNewState = ntv.uStateNew 93 | result.mOldState = ntv.uStateOld 94 | result.mNode = cast[TreeNode](cast[PVOID](ntv.lParam)) 95 | 96 | 97 | # Event properties 98 | proc x*(this: MouseEventArgs): int32 = this.mx 99 | proc y*(this: MouseEventArgs): int32 = this.my 100 | proc delta*(this: MouseEventArgs): int32 = this.mDelta 101 | proc shiftPressed*(this: MouseEventArgs): bool = this.mShiftPressed 102 | proc ctrlPressed*(this: MouseEventArgs): bool = this.mCtrlPressed 103 | proc mouseButton*(this: MouseEventArgs): MouseButtons = this.mButton -------------------------------------------------------------------------------- /Nimforms/font.nim: -------------------------------------------------------------------------------- 1 | # Created on 17-May-2025 14:06 2 | 3 | # Font related functions 4 | const 5 | LOGPIXELSY = 90 6 | DEFAULT_CHARSET = 1 7 | OUT_STRING_PRECIS = 1 8 | CLIP_DEFAULT_PRECIS = 0 9 | DEFAULT_QUALITY = 0 10 | 11 | proc createHandle(this: var Font) # Foreard declaration 12 | 13 | 14 | 15 | 16 | 17 | # proc updateFont*(this: var Font, src: Font) = 18 | 19 | 20 | proc newFont*(fname: string, fsize: int32, 21 | fweight: FontWeight = FontWeight.fwNormal, 22 | italic: bool = false, underline: bool = false, 23 | strikeout: bool = false, autoc: bool = false) : Font = 24 | # new(result) 25 | if fname.len > 31 : raise newException(OSError, "Length of font name exceeds 31 characters") 26 | result.name = fname 27 | result.size = fsize 28 | result.weight = fweight 29 | result.italics = italic 30 | result.underLine = underline 31 | result.strikeOut = strikeout 32 | # result.wtext = newWideString(fname) 33 | if autoc: result.createHandle() 34 | 35 | proc createHandle(this: var Font) = 36 | let scale = appData.scaleFactor / 100 37 | let fsiz = int32(scale * float(this.size)) 38 | let iHeight = -MulDiv(fsiz , appData.sysDPI, 72) 39 | var lf : LOGFONTW 40 | WideString.fillBuffer(lf.lfFaceName[0].addr, this.name) 41 | lf.lfItalic = cast[BYTE](this.italics) 42 | lf.lfUnderline = cast[BYTE](this.underLine) 43 | lf.lfHeight = iHeight 44 | lf.lfWeight = cast[LONG](this.weight) 45 | lf.lfCharSet = cast[BYTE](DEFAULT_CHARSET) 46 | lf.lfOutPrecision = cast[BYTE](OUT_STRING_PRECIS) 47 | lf.lfClipPrecision = cast[BYTE](CLIP_DEFAULT_PRECIS) 48 | lf.lfQuality = cast[BYTE](DEFAULT_QUALITY) 49 | lf.lfPitchAndFamily = 1 50 | this.handle = CreateFontIndirectW(lf.unsafeAddr) 51 | 52 | proc createPrimaryHandle(this: var Font) = 53 | # echo "sf ", appData.scaleFactor 54 | # let scale = int32(appData.scaleFactor / 100) 55 | # let fsiz = scale * this.size 56 | # let iHeight1 = cast[float](-MulDiv(fsiz , appData.sysDPI, 72)) 57 | let iHeight = -16 # iHeight1 #* appData.scaleF 58 | # echo "fsiz 2 - ", fsiz 59 | WideString.fillBuffer(appData.logfont.lfFaceName[0].addr, this.name) 60 | appData.logfont.lfItalic = cast[BYTE](this.italics) 61 | appData.logfont.lfUnderline = cast[BYTE](this.underLine) 62 | appData.logfont.lfHeight = int32(iHeight) 63 | appData.logfont.lfWeight = cast[LONG](this.weight) 64 | appData.logfont.lfCharSet = cast[BYTE](DEFAULT_CHARSET) 65 | appData.logfont.lfOutPrecision = cast[BYTE](OUT_STRING_PRECIS) 66 | appData.logfont.lfClipPrecision = cast[BYTE](CLIP_DEFAULT_PRECIS) 67 | appData.logfont.lfQuality = cast[BYTE](DEFAULT_QUALITY) 68 | appData.logfont.lfPitchAndFamily = 1 69 | this.handle = CreateFontIndirectW(appData.logfont.unsafeAddr) 70 | echo "font height ", iHeight, ", sys dpi ", appData.sysDPI 71 | 72 | proc cloneParentFontHandle(this: var Font, parentHandle: HFONT) = 73 | if parentHandle == nil: 74 | this.handle = CreateFontIndirectW(appData.logfont.addr) 75 | else: 76 | var lf : LOGFONTW 77 | let x = GetObjectW(parentHandle, cast[int32](sizeof(lf)), cast[LPVOID](lf.addr)) 78 | if x > 0 : 79 | if this.handle != nil: DeleteObject(this.handle) 80 | this.handle = CreateFontIndirectW(lf.addr) 81 | 82 | proc `=copy`*(dst: var Font, src: Font) = 83 | if dst.handle != nil: DeleteObject(dst.handle) 84 | dst.name = src.name 85 | dst.size = src.size 86 | dst.weight = src.weight 87 | dst.italics = src.italics 88 | dst.underLine = src.underLine 89 | dst.strikeOut = src.strikeOut 90 | var lf : LOGFONTW 91 | let x = GetObjectW(src.handle, cast[int32](sizeof(lf)), cast[LPVOID](lf.addr)) 92 | if x > 0: dst.handle = CreateFontIndirectW(lf.addr) 93 | # echo "dst name ", dst.name 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | proc finalize(this: var Font) = 102 | if this.handle != nil: 103 | # echo "Deleting my font handle" 104 | DeleteObject(this.handle) 105 | 106 | # End of Font related area 107 | 108 | -------------------------------------------------------------------------------- /Nimforms/graphics.nim: -------------------------------------------------------------------------------- 1 | # Created on 14-May-2025 08:27 2 | 3 | const 4 | DTT_TEXTCOLOR = 0x1 5 | BP_GROUPBOX = 0x4 6 | GBS_NORMAL = 0x1 7 | 8 | proc newGraphics(hw: HWND): Graphics = 9 | result.mHwnd = hw 10 | result.mHdc = GetDC(hw) 11 | result.mFree = true 12 | 13 | proc newGraphics(wp: WPARAM): Graphics = 14 | result.mHdc = cast[HDC](wp) 15 | result.mFree = false 16 | 17 | proc `=destroy`(this: var Graphics)= 18 | if this.mFree: 19 | ReleaseDC(this.mHwnd, this.mHdc) 20 | # echo "HDC Released" 21 | 22 | proc getTextSize(g: typedesc[Graphics], pc: Control): SIZE = 23 | var dc = GetDC(pc.mHandle) 24 | SelectObject(dc, pc.mFont.handle) 25 | GetTextExtentPoint32(dc, &pc.mWtext, pc.mWtext.mInputLen, result.unsafeAddr) 26 | let x = ReleaseDC(pc.mHandle, dc) 27 | # print "HDC released for "; pc.name 28 | 29 | proc drawHLine(this: Graphics, mPen: HPEN, sx, y, ex : int32) = 30 | SelectObject(this.mHdc, mPen) 31 | MoveToEx(this.mHdc, sx, y, nil) 32 | LineTo(this.mHdc, ex, y) 33 | 34 | proc drawText(this: Graphics, pc: Control, x, y: int32) = 35 | SetBkMode(this.mHdc, 1) 36 | SelectObject(this.mHdc, pc.mFont.handle) 37 | SetTextColor(this.mHdc, pc.mForeColor.cref) 38 | TextOut(this.mHdc, x, y, pc.mWtext.cptr, pc.mWtext.wcLen) 39 | 40 | proc drawThemeText(this: Graphics, pc: Control) = 41 | var hTheme = OpenThemeData(pc.mHandle, pc.mClassName) 42 | if hTheme != nil: 43 | # // Use themed text with custom color 44 | var opts : DTTOPTS 45 | opts.dwSize = cast[int32](sizeof(opts)) 46 | opts.dwFlags = DTT_TEXTCOLOR 47 | opts.crText = pc.mForeColor.cref #RGB(255, 0, 0) // Red text 48 | 49 | var rc : RECT 50 | GetClientRect(pc.mHandle, rc.unsafeAddr) 51 | rc.left += 9 # // Offset to align text 52 | rc.top += 1 53 | 54 | DrawThemeTextEx( 55 | hTheme, this.mHdc, BP_GROUPBOX, GBS_NORMAL, pc.mWtext.cptr, 56 | pc.mWtext.wcLen, DT_LEFT or DT_SINGLELINE, rc.unsafeAddr, opts.unsafeAddr 57 | ) 58 | CloseThemeData(hTheme) 59 | -------------------------------------------------------------------------------- /Nimforms/groupbox.nim: -------------------------------------------------------------------------------- 1 | # groupbox module Created on 31-Mar-2023 11:14 PM; Author kcvinker 2 | 3 | #[========================================GroupBox Docs==================================================== 4 | constructor - newGroupBox* 5 | functions 6 | createHandle() - Create the handle of GroupBox 7 | 8 | Properties: 9 | All props inherited from Control type 10 | font Font (See commons.nim) 11 | text string 12 | width int32 13 | height int32 14 | xpos int32 15 | ypos int32 16 | backColor Color (See colors.nim) 17 | foreColor Color 18 | style GroupBoxStyle (enum, it determines how to draw group box) 19 | 20 | Events: 21 | All events inherited from Control type 22 | =========================================================================================================]# 23 | # Constants 24 | # const 25 | 26 | var gbCount = 1 27 | let penwidth : int32 = 4 28 | 29 | let gbStyle: DWORD = WS_CHILD or WS_VISIBLE or BS_GROUPBOX or BS_NOTIFY or BS_TOP or WS_OVERLAPPED or WS_CLIPCHILDREN or WS_CLIPSIBLINGS 30 | 31 | # Forward declaration 32 | proc gbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 33 | proc createHandle*(this: GroupBox) 34 | proc newLabel*(parent: Form, text: string, x: int32 = 10, y: int32 = 10, w: int32 = 0, h: int32 = 0): Label 35 | # GroupBox constructor 36 | proc newGroupBox*(parent: Form, text: string, x: int32 = 10, y: int32 = 10, w: int32 = 150, 37 | h: int32 = 150, style: GroupBoxStyle = GroupBoxStyle.gbsSystem ): GroupBox = 38 | new(result) 39 | result.mKind = ctGroupBox 40 | result.mClassName = cast[LPCWSTR](BtnClass[0].addr) 41 | result.mName = "GroupBox_" & $gbCount 42 | result.mParent = parent 43 | result.mXpos = x 44 | result.mYpos = y 45 | result.mWidth = w 46 | result.mHeight = h 47 | result.mText = text 48 | result.mWtext = newWideString(text) 49 | result.cloneParentFont() 50 | result.mHasFont = true 51 | result.mHasText = true 52 | result.mDBFill = true 53 | result.mGetWidth = true 54 | result.mBackColor = parent.mBackColor 55 | result.mForeColor = CLR_BLACK 56 | result.mStyle = gbStyle 57 | result.mGBStyle = style 58 | result.mExStyle = WS_EX_CONTROLPARENT #or WS_EX_TRANSPARENT 59 | gbCount += 1 60 | parent.mControls.add(result) 61 | if parent.mCreateChilds: result.createHandle() 62 | 63 | proc addControls*(this: GroupBox, args: varargs[Control]) = 64 | for item in args: 65 | this.mControls.add(item) 66 | if item.mKind == ControlType.ctLabel: 67 | item.mBackColor = this.mBackColor 68 | 69 | # proc doubleBufferFill(this: GroupBox) = 70 | # var hdc : HDC = GetDC(this.mHandle) 71 | # var size : SIZE 72 | # SelectObject(hdc, this.mFont.handle) 73 | # GetTextExtentPoint32(hdc, &this.mWtext, this.mWtext.wcLen, size.unsafeAddr) 74 | # ReleaseDC(this.mHandle, hdc) 75 | # this.mTextWidth = size.cx + 10 76 | 77 | # Create GroupBox's hwnd 78 | proc createHandle*(this: GroupBox) = 79 | this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 80 | if this.mGBStyle == GroupBoxStyle.gbsOverride: 81 | this.mPen = CreatePen(PS_SOLID, penwidth, this.mBackColor.cref) 82 | #---------------------------------------------------------------- 83 | this.mRect = RECT(left: 0, top: 0, right: this.mWidth, bottom: this.mHeight) 84 | this.createHandleInternal() 85 | if this.mHandle != nil: 86 | if this.mGBStyle == GroupBoxStyle.gbsClassic: 87 | SetWindowTheme(this.mHandle, emptyWStrPtr, emptyWStrPtr) 88 | this.mThemeOff = true 89 | #--------------------------- 90 | this.setSubclass(gbWndProc) 91 | this.setFontInternal() 92 | 93 | 94 | proc resetGdiObjects(this: GroupBox, brpn: bool) = 95 | # brpn = Reset Hbrush and Hpen 96 | if brpn: 97 | if this.mBkBrush != nil: DeleteObject(this.mBkBrush) 98 | this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 99 | if this.mGBStyle == GroupBoxStyle.gbsOverride: 100 | if this.mPen != nil: DeleteObject(this.mPen) 101 | this.mPen = CreatePen(PS_SOLID, penwidth, this.mBackColor.cref) 102 | #------------------------------------------------ 103 | if this.mHdc != nil: DeleteDC(this.mHdc) 104 | if this.mBmp != nil: DeleteObject(this.mBmp) 105 | this.mDBFill = true 106 | 107 | # Overriding Control's property because, groupBox needs a different treatment 108 | proc `backColor=`*(this: GroupBox, clr: uint) = 109 | this.mBackColor = newColor(clr) 110 | this.resetGdiObjects(true) 111 | this.checkRedraw() 112 | 113 | proc setforeColor*(this: GroupBox, value: uint, style: GroupBoxStyle = GroupBoxStyle.gbsClassic) = 114 | this.mForeColor = newColor(value) 115 | this.mGBStyle = style 116 | if this.mGBStyle == GroupBoxStyle.gbsClassic: 117 | if not this.mThemeOff: 118 | SetWindowTheme(this.mHandle, emptyWStrPtr, emptyWStrPtr) 119 | this.mThemeOff = true 120 | #----------------- 121 | if this.mGBStyle == GroupBoxStyle.gbsOverride: 122 | this.mGetWidth = true 123 | if this.mPen == nil: this.mPen = CreatePen(PS_SOLID, penwidth, this.mBackColor.cref) 124 | #------------------------ 125 | this.checkRedraw() 126 | 127 | 128 | proc `text=`*(this: GroupBox, txt: string) = 129 | this.mText = txt 130 | this.mWtext.updateBuffer(txt) 131 | this.mGetWidth = true 132 | if this.mIsCreated: SetWindowTextW(this.mHandle, &this.mWtext) 133 | this.checkRedraw() 134 | 135 | proc `width=`*(this: GroupBox, value: int32) = 136 | this.mWidth = value 137 | this.resetGdiObjects(false) 138 | if this.mIsCreated: this.ctlSetPos() 139 | 140 | proc `height=`*(this: GroupBox, value: int32) = 141 | this.mHeight = value 142 | this.resetGdiObjects(false) 143 | if this.mIsCreated: this.ctlSetPos() 144 | 145 | proc `font=`*(this: GroupBox, value: Font) = 146 | this.mFont = value 147 | if this.mFont.handle == nil: this.mFont.createHandle() 148 | this.sendMsg(WM_SETFONT, this.mFont.handle, 1) 149 | this.mGetWidth = true 150 | this.checkRedraw() 151 | 152 | proc `style=`*(this: GroupBox, value: GroupBoxStyle) = 153 | this.mGBStyle = value 154 | if value == GroupBoxStyle.gbsClassic: 155 | if not this.mThemeOff: 156 | # this.mGBStyle = GroupBoxStyle.gbsClassic 157 | SetWindowTheme(this.mHandle, emptyWStrPtr, emptyWStrPtr) 158 | this.mThemeOff = true 159 | #-------------------------------- 160 | #------------------------------------- 161 | if value == GroupBoxStyle.gbsOverride: 162 | this.mGetWidth = true 163 | if this.mPen == nil: this.mPen = CreatePen(PS_SOLID, penwidth, this.mBackColor.cref) 164 | #---------------------------------------------------------- 165 | if this.mIsCreated: InvalidateRect(this.mHandle, nil, 0) 166 | 167 | proc changeFont*(this: GroupBox, fname: string, fsize: int32, fweight: FontWeight = FontWeight.fwNormal) = 168 | this.mFont.name = fname 169 | this.mFont.size = fsize 170 | this.mFont.weight = fweight 171 | this.mFont.createHandle() 172 | this.sendMsg(WM_SETFONT, this.mFont.handle, 1) 173 | this.mGetWidth = true 174 | this.checkRedraw() 175 | 176 | 177 | 178 | method autoCreate(this: GroupBox) = this.createHandle() 179 | 180 | proc gbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 181 | # echo msg 182 | case msg 183 | of WM_DESTROY: 184 | RemoveWindowSubclass(hw, gbWndProc, scID) 185 | var this = cast[GroupBox](refData) 186 | if this.mPen != nil: DeleteObject(this.mPen) 187 | if this.mHdc != nil: DeleteDC(this.mHdc) 188 | if this.mBmp != nil: DeleteObject(this.mBmp) 189 | this.destructor() 190 | 191 | of WM_LBUTTONDOWN: 192 | var this = cast[GroupBox](refData) 193 | this.leftButtonDownHandler(msg, wpm, lpm) 194 | 195 | of WM_LBUTTONUP: 196 | var this = cast[GroupBox](refData) 197 | this.leftButtonUpHandler(msg, wpm, lpm) 198 | 199 | of WM_RBUTTONDOWN: 200 | var this = cast[GroupBox](refData) 201 | this.rightButtonDownHandler(msg, wpm, lpm) 202 | 203 | of WM_RBUTTONUP: 204 | var this = cast[GroupBox](refData) 205 | this.rightButtonUpHandler(msg, wpm, lpm) 206 | 207 | of WM_MOUSEMOVE: 208 | var this = cast[GroupBox](refData) 209 | this.mouseMoveHandler(msg, wpm, lpm) 210 | 211 | of WM_MOUSELEAVE: 212 | var this = cast[GroupBox](refData) 213 | this.mouseLeaveHandler() 214 | 215 | of WM_GETTEXTLENGTH: 216 | var this = cast[GroupBox](refData) 217 | if this.mGBStyle == GroupBoxStyle.gbsOverride: 218 | return 0 219 | # else: 220 | # return DefSubclassProc(hw, msg, wpm, lpm) 221 | 222 | of WM_CONTEXTMENU: 223 | var this = cast[GroupBox](refData) 224 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 225 | 226 | of WM_ERASEBKGND: 227 | var this = cast[GroupBox](refData) 228 | let hdc = cast[HDC](wpm) 229 | if this.mGetWidth: 230 | var size : SIZE 231 | SelectObject(hdc, this.mFont.handle) 232 | GetTextExtentPoint32(hdc, &this.mWtext, this.mWtext.wcLen, size.unsafeAddr) 233 | this.mTextWidth = size.cx + 10 234 | this.mGetWidth = false 235 | #------------------------------ 236 | if this.mDBFill: 237 | this.mHdc = CreateCompatibleDC(hdc) 238 | this.mBmp = CreateCompatibleBitmap(hdc, this.mWidth, this.mHeight) 239 | SelectObject(this.mHdc, this.mBmp) 240 | FillRect(this.mHdc, &this.mRect, this.mBkBrush) 241 | this.mDBFill = false 242 | #------------------------------------ 243 | BitBlt(hdc, 0, 0, this.mWidth, this.mHeight, this.mHdc, 0, 0, SRCCOPY) 244 | return 1 245 | 246 | 247 | of MM_LABEL_COLOR: 248 | var this = cast[GroupBox](refData) 249 | if this.mGBStyle == GroupBoxStyle.gbsClassic: 250 | var hdc = cast[HDC](wpm) 251 | SetBkMode(hdc, 1) 252 | # SelectObject(hdc, cast[HGDIOBJ](this.mFont.handle)) 253 | SetTextColor(hdc, this.mForeColor.cref) 254 | 255 | return cast[LRESULT](this.mBkBrush) 256 | 257 | 258 | of WM_PAINT: 259 | var this = cast[GroupBox](refData) 260 | if this.mGBStyle == GroupBoxStyle.gbsOverride: 261 | let ret = DefSubclassProc(hw, msg, wpm, lpm) 262 | let gfx = newGraphics(hw) 263 | gfx.drawHLine(this.mPen, 10, 10, this.mTextWidth) 264 | gfx.drawText(this, 12, 0) 265 | # gfx.drawThemeText(this) 266 | 267 | # return ret 268 | # else: 269 | # var ps : PAINTSTRUCT 270 | # BeginPaint(hw, ps.unsafeAddr) 271 | # EndPaint(hw, ps.unsafeAddr) 272 | 273 | 274 | else: return DefSubclassProc(hw, msg, wpm, lpm) 275 | return DefSubclassProc(hw, msg, wpm, lpm) 276 | -------------------------------------------------------------------------------- /Nimforms/label.nim: -------------------------------------------------------------------------------- 1 | # label module Created on 01-Apr-2023 12:32 AM; Author kcvinker 2 | #[==========================================Label Docs========================================== 3 | Constructor - newLabel 4 | Functions 5 | createHandle() - Create the handle of Label 6 | 7 | Properties: 8 | All props inherited from Control type 9 | autoSize bool 10 | multiLine bool 11 | textAlign TextAlignment - Enum (See typemodule.nim) 12 | borderStyle LabelBorder - Enum (See typemodule.nim) 13 | 14 | Events 15 | All events inherited from Control type 16 | ================================================================================================]# 17 | # Constants 18 | const 19 | SS_NOTIFY = 0x00000100 20 | SS_SUNKEN = 0x00001000 21 | SWP_NOMOVE = 0x0002 22 | SIZE_FLAG = SWP_NOMOVE #or SWP_NOZORDER #or SWP_NOACTIVATE 23 | var lbCount = 1 24 | # let lbClsName = toWcharPtr("Static") 25 | let lbClsName : array[7, uint16] = [0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0] 26 | 27 | 28 | # Forward declaration 29 | proc lbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 30 | proc createHandle*(this: Label) 31 | # Label constructor 32 | proc newLabel*(parent: Form, text: string, x: int32 = 10, y: int32 = 10, w: int32 = 0, h: int32 = 0): Label = 33 | new(result) 34 | result.mKind = ctLabel 35 | result.mClassName = cast[LPCWSTR](lbClsName[0].addr) 36 | result.mName = "Label_" & $lbCount 37 | result.mParent = parent 38 | result.mXpos = x 39 | result.mYpos = y 40 | result.mWidth = w 41 | result.mHeight = h 42 | result.mText = text 43 | result.mWtext = newWideString(text) 44 | result.cloneParentFont() 45 | result.mHasFont = true 46 | result.mHasText = true 47 | result.mBackColor = parent.mBackColor 48 | result.mForeColor = CLR_BLACK 49 | result.mAutoSize = true 50 | result.mMultiLine = false 51 | result.mStyle = WS_VISIBLE or WS_CHILD or WS_CLIPCHILDREN or WS_CLIPSIBLINGS or SS_NOTIFY 52 | result.mExStyle = 0 53 | lbCount += 1 54 | parent.mControls.add(result) 55 | if parent.mCreateChilds: createHandle(result) 56 | 57 | 58 | proc setLbStyle(this: Label) = 59 | if this.mBorder != lbNone: 60 | this.mStyle = (if this.mBorder == lbSunken: this.mStyle or SS_SUNKEN else: this.mStyle or WS_BORDER) 61 | if this.mMultiLine or this.mWidth > 0 or this.mHeight > 0: this.mAutoSize = false 62 | this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 63 | 64 | 65 | proc setAutoSize(this: Label, redraw: bool) = 66 | var hdc: HDC = GetDC(this.mHandle) 67 | var ss : SIZE 68 | SelectObject(hdc, this.mFont.handle) 69 | GetTextExtentPoint32(hdc, this.mText.toWcharPtr, int32(this.mText.len), ss.unsafeAddr) 70 | ReleaseDC(this.mHandle, hdc) 71 | this.mWidth = ss.cx #+ 5 72 | this.mHeight = ss.cy 73 | SetWindowPos(this.mHandle, nil, this.mXpos, this.mYpos, this.mWidth, this.mHeight, SIZE_FLAG) #SWP_NOMOVE) 74 | if redraw: InvalidateRect(this.mHandle, nil, 1) 75 | # echo "After autosize x : ", this.mXpos, ", y: ", this.mYpos #this.mXpos, this.mYpos 76 | 77 | 78 | # Create Label's hwnd 79 | proc createHandle*(this: Label) = 80 | this.setLbStyle() 81 | this.createHandleInternal() 82 | if this.mHandle != nil: 83 | # echo "lb mh" 84 | this.setSubclass(lbWndProc) 85 | this.setFontInternal() 86 | if this.mAutoSize: this.setAutoSize(false) 87 | # echo "lbl hwnd ", cast[int](this.mHandle) 88 | 89 | method autoCreate(this: Label) = this.createHandle() 90 | 91 | proc `autoSize=`*(this: Label, value: bool) {.inline.} = this.mAutoSize = value 92 | proc autoSize*(this: Label): bool {.inline.} = this.mAutoSize 93 | 94 | proc `multiLine=`*(this: Label, value: bool) {.inline.} = this.mMultiLine = value 95 | proc multiLine*(this: Label): bool {.inline.} = this.mMultiLine 96 | 97 | proc `textAlign=`*(this: Label, value: TextAlignment) {.inline.} = this.mTextAlign = value 98 | proc textAlign*(this: Label): TextAlignment {.inline.} = this.mTextAlign 99 | 100 | proc `borderStyle=`*(this: Label, value: LabelBorder) {.inline.} = this.mBorder = value 101 | proc borderStyle*(this: Label): LabelBorder {.inline.} = this.mBorder 102 | 103 | 104 | 105 | 106 | proc lbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 107 | 108 | case msg 109 | of WM_DESTROY: 110 | RemoveWindowSubclass(hw, lbWndProc, scID) 111 | var this = cast[Label](refData) 112 | this.destructor() 113 | 114 | of WM_LBUTTONDOWN: 115 | var this = cast[Label](refData) 116 | this.leftButtonDownHandler(msg, wpm, lpm) 117 | of WM_LBUTTONUP: 118 | var this = cast[Label](refData) 119 | this.leftButtonUpHandler(msg, wpm, lpm) 120 | of WM_RBUTTONDOWN: 121 | var this = cast[Label](refData) 122 | this.rightButtonDownHandler(msg, wpm, lpm) 123 | of WM_RBUTTONUP: 124 | var this = cast[Label](refData) 125 | this.rightButtonUpHandler(msg, wpm, lpm) 126 | of WM_MOUSEMOVE: 127 | var this = cast[Label](refData) 128 | this.mouseMoveHandler(msg, wpm, lpm) 129 | of WM_MOUSELEAVE: 130 | var this = cast[Label](refData) 131 | this.mouseLeaveHandler() 132 | of WM_CONTEXTMENU: 133 | var this = cast[Label](refData) 134 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 135 | 136 | of MM_LABEL_COLOR: 137 | var this = cast[Label](refData) 138 | # echo "MMLABEL Color" 139 | # this.printControlRect() 140 | let hdc = cast[HDC](wpm) 141 | if (this.mDrawMode and 1) == 1: SetTextColor(hdc, this.mForeColor.cref) 142 | SetBkColor(hdc, this.mBackColor.cref) 143 | return cast[LRESULT](this.mBkBrush) 144 | 145 | else: return DefSubclassProc(hw, msg, wpm, lpm) 146 | return DefSubclassProc(hw, msg, wpm, lpm) 147 | -------------------------------------------------------------------------------- /Nimforms/listbox.nim: -------------------------------------------------------------------------------- 1 | # listbox module Created on 01-Apr-2023 03:55 AM; Author kcvinker 2 | #[===========================================ListBox Docs======================================== 3 | constructor - newListBox 4 | functions 5 | createHandle() - Create the handle of listBox 6 | selectAll*() 7 | clearSelection*() 8 | addItem*( item: auto) 9 | addItems*(args: varargs[string, `$`]) 10 | insertItem*(item: auto, index: int32) 11 | removeItem*(item: auto) 12 | removeItem*(index: int32) 13 | removeAll*() 14 | indexOf*(item: auto): int32 15 | 16 | Properties: 17 | All props inherited from Control type 18 | items : seq[string] 19 | hotIndex : int32 20 | hotItem : string 21 | horizontalScroll : bool 22 | verticalScroll : bool 23 | selectedIndex : int32 24 | selectedIndices : seq[int32] 25 | multiSelection : bool 26 | selectedItem : string 27 | selctedItems : seq[string] 28 | 29 | Events 30 | All events inherited from Control type 31 | EventHandler - proc(c: Control, e: EventArgs) 32 | onSelectionChanged 33 | onSelectionCancelled 34 | =======================================================================================================]# 35 | # Constants 36 | const 37 | # LB_CTLCODE = 0 38 | # LB_OKAY = 0 39 | LB_ERR = -1 40 | # LB_ERRSPACE = -2 41 | # LBN_ERRSPACE = -2 42 | LBN_SELCHANGE = 1 43 | LBN_DBLCLK = 2 44 | LBN_SELCANCEL = 3 45 | LBN_SETFOCUS = 4 46 | LBN_KILLFOCUS = 5 47 | LB_ADDSTRING = 0x0180 48 | LB_INSERTSTRING = 0x0181 49 | LB_DELETESTRING = 0x0182 50 | LB_SELITEMRANGEEX = 0x0183 51 | LB_RESETCONTENT = 0x0184 52 | LB_SETSEL = 0x0185 53 | LB_SETCURSEL = 0x0186 54 | LB_GETSEL = 0x0187 55 | LB_GETCURSEL = 0x0188 56 | LB_GETTEXT = 0x0189 57 | LB_GETTEXTLEN = 0x018A 58 | LB_GETCOUNT = 0x018B 59 | LB_SELECTSTRING = 0x018C 60 | # LB_DIR = 0x018D 61 | # LB_GETTOPINDEX = 0x018E 62 | # LB_FINDSTRING = 0x018F 63 | LB_GETSELCOUNT = 0x0190 64 | LB_GETSELITEMS = 0x0191 65 | # LB_SETTABSTOPS = 0x0192 66 | # LB_GETHORIZONTALEXTENT = 0x0193 67 | # LB_SETHORIZONTALEXTENT = 0x0194 68 | # LB_SETCOLUMNWIDTH = 0x0195 69 | # LB_ADDFILE = 0x0196 70 | # LB_SETTOPINDEX = 0x0197 71 | # LB_GETITEMRECT = 0x0198 72 | # LB_GETITEMDATA = 0x0199 73 | # LB_SETITEMDATA = 0x019A 74 | # LB_SELITEMRANGE = 0x019B 75 | # LB_SETANCHORINDEX = 0x019C 76 | # LB_GETANCHORINDEX = 0x019D 77 | # LB_SETCARETINDEX = 0x019E 78 | LB_GETCARETINDEX = 0x019F 79 | # LB_SETITEMHEIGHT = 0x01A0 80 | # LB_GETITEMHEIGHT = 0x01A1 81 | LB_FINDSTRINGEXACT = 0x01A2 82 | # LB_SETLOCALE = 0x01A5 83 | # LB_GETLOCALE = 0x01A6 84 | # LB_SETCOUNT = 0x01A7 85 | # LB_INITSTORAGE = 0x01A8 86 | # LB_ITEMFROMPOINT = 0x01A9 87 | # LB_MULTIPLEADDSTRING = 0x01B1 88 | # LB_GETLISTBOXINFO = 0x01B2 89 | # LB_MSGMAX = 0x01B3 90 | LBS_NOTIFY = 0x0001 91 | LBS_SORT = 0x0002 92 | LBS_NOREDRAW = 0x0004 93 | LBS_MULTIPLESEL = 0x0008 94 | LBS_OWNERDRAWFIXED = 0x0010 95 | LBS_OWNERDRAWVARIABLE = 0x0020 96 | LBS_HASSTRINGS = 0x0040 97 | LBS_USETABSTOPS = 0x0080 98 | LBS_NOINTEGRALHEIGHT = 0x0100 99 | LBS_MULTICOLUMN = 0x0200 100 | LBS_WANTKEYBOARDINPUT = 0x0400 101 | LBS_EXTENDEDSEL = 0x0800 102 | LBS_DISABLENOSCROLL = 0x1000 103 | LBS_NODATA = 0x2000 104 | LBS_NOSEL = 0x4000 105 | LBS_COMBOBOX = 0x8000 106 | LBS_STANDARD = LBS_NOTIFY or LBS_SORT or WS_VSCROLL or WS_BORDER 107 | 108 | 109 | var lbxCount = 1 110 | # let lbxClsName = toWcharPtr("Listbox") 111 | let lbxClsName : array[8, uint16] = [0x4C, 0x69, 0x73, 0x74, 0x62, 0x6F, 0x78, 0] 112 | 113 | 114 | # Forward declaration 115 | proc lbxWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 116 | proc createHandle*(this: ListBox) 117 | 118 | # ListBox constructor 119 | proc newListBox*(parent: Form, x: int32 = 10, y: int32 = 10, w: int32 = 140, h: int32 = 140 ): ListBox = 120 | new(result) 121 | result.mKind = ctListBox 122 | result.mClassName = cast[LPCWSTR](lbxClsName[0].addr) 123 | result.mName = "ListBox_" & $lbxCount 124 | result.mParent = parent 125 | result.mXpos = x 126 | result.mYpos = y 127 | result.mWidth = w 128 | result.mHeight = h 129 | # result.mFont = parent.mFont 130 | result.cloneParentFont() 131 | result.mHasFont = true 132 | result.mBackColor = CLR_WHITE 133 | result.mForeColor = CLR_BLACK 134 | result.mDummyIndex = -1 135 | result.mSelIndex = -1 136 | result.mStyle = WS_VISIBLE or WS_CHILD or WS_BORDER or LBS_NOTIFY or LBS_HASSTRINGS 137 | result.mExStyle = 0 138 | lbxCount += 1 139 | parent.mControls.add(result) 140 | if parent.mCreateChilds: result.createHandle() 141 | 142 | proc setLbxStyle(this: ListBox) = 143 | if this.mHasSort: this.mStyle = this.mStyle or LBS_SORT 144 | if this.mMultiSel: this.mStyle = this.mStyle or LBS_EXTENDEDSEL or LBS_MULTIPLESEL 145 | if this.mMultiColumn: this.mStyle = this.mStyle or LBS_MULTICOLUMN 146 | if this.mNoSelection: this.mStyle = this.mStyle or LBS_NOSEL 147 | if this.mKeyPreview: this.mStyle = this.mStyle or LBS_WANTKEYBOARDINPUT 148 | if this.mHorizScroll: this.mStyle = this.mStyle or WS_HSCROLL 149 | if this.mVertScroll: this.mStyle = this.mStyle or WS_VSCROLL 150 | this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 151 | 152 | proc manageItems(this: ListBox) = 153 | for item in this.mItems: 154 | appData.sendMsgBuffer.updateBuffer(item) 155 | this.sendMsg(LB_ADDSTRING, 0, &appData.sendMsgBuffer) 156 | 157 | if this.mDummyIndex > -1: this.sendMsg(LB_SETCURSEL, this.mDummyIndex, 0) 158 | 159 | proc getItemInternal(this: ListBox, index: int32) : string = 160 | let iLen = int32(this.sendMsg(LB_GETTEXTLEN, index, 0)) 161 | appData.sendMsgBuffer.ensureSize(iLen + 1) 162 | this.sendMsg(LB_GETTEXT, index, &appData.sendMsgBuffer) 163 | result = appData.sendMsgBuffer.toStr 164 | 165 | 166 | # Create ListBox's hwnd 167 | proc createHandle*(this: ListBox) = 168 | this.setLbxStyle() 169 | this.createHandleInternal() 170 | if this.mHandle != nil: 171 | this.setSubclass(lbxWndProc) 172 | this.setFontInternal() 173 | if this.mItems.len > 0: this.manageItems() 174 | 175 | method autoCreate(this: ListBox) = this.createHandle() 176 | 177 | # Public functions------------------------------------------------------------- 178 | proc indexOf*(this: ListBox, item: auto): int32 {.inline.} = 179 | result = -1 180 | if this.mIsCreated: 181 | let sitem : string = (if item is string: item else: $item) 182 | appData.sendMsgBuffer.updateBuffer(sitem) 183 | result = int32(this.sendMsg(LB_FINDSTRINGEXACT, -1, &appData.sendMsgBuffer)) 184 | 185 | proc selectAll*(this: ListBox) = 186 | if this.mIsCreated and this.mMultiSel: this.sendMsg(LB_SETSEL, 1, -1) 187 | 188 | proc clearSelection*(this: ListBox) = 189 | if this.mIsCreated: 190 | if this.mMultiSel: 191 | this.sendMsg(LB_SETSEL, 0, -1) 192 | else: 193 | this.sendMsg(LB_SETCURSEL, -1, 0) 194 | 195 | proc addItem*(this: ListBox, item: auto) = 196 | let sitem : string = (if item is string: item else: $item) 197 | appData.sendMsgBuffer.updateBuffer(sitem) 198 | if this.mIsCreated: this.sendMsg(LB_ADDSTRING, 0, &appData.sendMsgBuffer) 199 | this.mItems.add(sitem) 200 | 201 | proc addItems*(this: ListBox, args: varargs[string, `$`]) = 202 | for item in args: 203 | if this.mIsCreated: 204 | appData.sendMsgBuffer.updateBuffer(item) 205 | this.sendMsg(LB_ADDSTRING, 0, &appData.sendMsgBuffer) 206 | this.mItems.add(item) 207 | 208 | proc insertItem*(this: ListBox, item: auto, index: int32) = 209 | if this.mIsCreated: 210 | let sitem : string = (if item is string: item else: $item) 211 | appData.sendMsgBuffer.updateBuffer(sitem) 212 | this.sendMsg(LB_INSERTSTRING, index, &appData.sendMsgBuffer) 213 | this.mItems.insert(index, sitem) 214 | 215 | proc removeItem*(this: ListBox, item: auto) = 216 | if this.mIsCreated: 217 | let sitem : string = (if item is string: item else: $item) 218 | appData.sendMsgBuffer.updateBuffer(sitem) 219 | let index = int32(this.sendMsg(LB_FINDSTRINGEXACT, -1, &appData.sendMsgBuffer)) 220 | if index != LB_ERR: 221 | this.sendMsg(LB_DELETESTRING, index, 0) 222 | this.mItems = filter(seq, proc(x: string): bool = x != sitem) 223 | 224 | proc removeItem*(this: ListBox, index: int32) = 225 | if this.mIsCreated and index > -1: 226 | this.sendMsg(LB_DELETESTRING, index, 0) 227 | this.mItems.delete(index) 228 | 229 | proc removeAll*(this: ListBox) = 230 | if this.mItems.len > 0: 231 | this.mItems = @[] 232 | if this.mIsCreated: this.sendMsg(LB_RESETCONTENT, 0, 0) 233 | 234 | 235 | # Properties ----------------------------------------------------------------------- 236 | proc items*(this: ListBox): seq[string] {.inline.} = this.mItems 237 | 238 | proc `horizontalScroll=`*(this: ListBox, value: bool) {.inline.} = this.mHorizScroll = value 239 | proc horizontalScroll*(this: ListBox): bool {.inline.} = this.mHorizScroll 240 | 241 | proc `verticalScroll=`*(this: ListBox, value: bool) {.inline.} = this.mVertScroll = value 242 | proc verticalScroll*(this: ListBox): bool {.inline.} = this.mVertScroll 243 | 244 | proc `selectedIndex=`*(this: ListBox, value: int32) = 245 | if this.mIsCreated and not this.mMultiSel: 246 | this.mSelIndex = int32(this.sendMsg(LB_SETCURSEL, 0, 0)) 247 | if not this.mIsCreated: 248 | this.mDummyIndex = value 249 | this.mSelIndex = value 250 | 251 | proc selectedIndex*(this: ListBox): int32 = 252 | if this.mIsCreated and not this.mMultiSel: 253 | result = int32(this.sendMsg(LB_GETCURSEL, 0, 0)) 254 | else: 255 | result = -1 256 | 257 | proc selectedIndices*(this: ListBox): seq[int32] = 258 | result = @[] 259 | if this.mIsCreated and this.mMultiSel: 260 | let selCount = int32(this.sendMsg(LB_GETSELCOUNT, 0, 0)) 261 | if selCount != LB_ERR: 262 | if this.mSelIndices.len == 0: 263 | this.mSelIndices = newSeq[int32](selCount) 264 | else: 265 | this.mSelIndices.setLen(0) 266 | this.mSelIndices.setLen(selCount) 267 | this.sendMsg(LB_GETSELITEMS, selCount, this.mSelIndices[0].unsafeAddr) 268 | result = this.mSelIndices 269 | 270 | 271 | proc `multiSelection=`*(this: ListBox, value: bool ) {.inline.} = this.mMultiSel = value 272 | proc multiSelection*(this: ListBox): bool {.inline.} = this.mMultiSel 273 | 274 | proc `selectedItem=`*(this: ListBox, value: auto) = 275 | if this.mIsCreated and this.mItems.len > 0: 276 | let sitem : string = (if value is string: value else: $value) 277 | appData.sendMsgBuffer.updateBuffer(sitem) 278 | let index = int32(this.sendMsg(LB_FINDSTRINGEXACT, -1, &appData.sendMsgBuffer)) 279 | if index != LB_ERR: this.sendMsg(LB_SETCURSEL, index, 0) 280 | 281 | proc selctedItem*(this: ListBox): string = 282 | result = "" 283 | if this.mIsCreated: 284 | this.mSelIndex = int32(this.sendMsg(LB_GETCURSEL, 0, 0)) 285 | if this.mSelIndex != LB_ERR: result = this.getItemInternal(this.mSelIndex) 286 | 287 | proc selctedItems*(this: ListBox): seq[string] = 288 | result = @[] 289 | if this.mIsCreated and this.mMultiSel: 290 | let selCount = int32(this.sendMsg(LB_GETSELCOUNT, 0, 0)) 291 | if selCount != LB_ERR: 292 | var buffer = newSeq[int](selCount) 293 | this.sendMsg(LB_GETSELITEMS, selCount, buffer[0].unsafeAddr) 294 | for index in buffer: result.add(this.getItemInternal(int32(index))) 295 | 296 | proc hotIndex*(this: ListBox): int32 {.inline.} = 297 | result = -1 298 | if this.mMultiSel: result = int32(this.sendMsg(LB_GETCARETINDEX, 0, 0)) 299 | 300 | proc hotItem*(this: ListBox): string = 301 | result = "" 302 | if this.mMultiSel: 303 | let hindex = int32(this.sendMsg(LB_GETCARETINDEX, 0, 0)) 304 | if hindex != LB_ERR: result = this.getItemInternal(hindex) 305 | 306 | 307 | 308 | proc lbxWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 309 | 310 | case msg 311 | of WM_DESTROY: 312 | RemoveWindowSubclass(hw, lbxWndProc, scID) 313 | var this = cast[ListBox](refData) 314 | this.destructor() 315 | 316 | of WM_LBUTTONDOWN: 317 | var this = cast[ListBox](refData) 318 | this.leftButtonDownHandler(msg, wpm, lpm) 319 | 320 | of WM_LBUTTONUP: 321 | var this = cast[ListBox](refData) 322 | this.leftButtonUpHandler(msg, wpm, lpm) 323 | 324 | of WM_RBUTTONDOWN: 325 | var this = cast[ListBox](refData) 326 | this.rightButtonDownHandler(msg, wpm, lpm) 327 | 328 | of WM_RBUTTONUP: 329 | var this = cast[ListBox](refData) 330 | this.rightButtonUpHandler(msg, wpm, lpm) 331 | 332 | of WM_MOUSEMOVE: 333 | var this = cast[ListBox](refData) 334 | this.mouseMoveHandler(msg, wpm, lpm) 335 | 336 | of WM_MOUSELEAVE: 337 | var this = cast[ListBox](refData) 338 | this.mouseLeaveHandler() 339 | 340 | of WM_KEYDOWN: 341 | var this = cast[ListBox](refData) 342 | this.keyDownHandler(wpm) 343 | 344 | of WM_KEYUP: 345 | var this = cast[ListBox](refData) 346 | this.keyUpHandler(wpm) 347 | 348 | of WM_CHAR: 349 | var this = cast[ListBox](refData) 350 | this.keyPressHandler(wpm) 351 | 352 | of WM_CONTEXTMENU: 353 | var this = cast[ListBox](refData) 354 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 355 | 356 | of MM_LIST_COLOR: 357 | var this = cast[ListBox](refData) 358 | let hdc = cast[HDC](wpm) 359 | if (this.mDrawMode and 1) == 1: SetTextColor(hdc, this.mForeColor.cref) 360 | SetBkColor(hdc, this.mBackColor.cref) 361 | return cast[LRESULT](this.mBkBrush) 362 | 363 | of MM_CTL_COMMAND: 364 | var this = cast[ListBox](refData) 365 | let ncode = HIWORD(wpm) 366 | case ncode 367 | of LBN_DBLCLK: 368 | if this.onDoubleClick != nil: this.onDoubleClick(this, newEventArgs()) 369 | of LBN_KILLFOCUS: 370 | if this.onLostFocus != nil: this.onLostFocus(this, newEventArgs()) 371 | of LBN_SELCHANGE: 372 | if this.onSelectionChanged != nil: this.onSelectionChanged(this, newEventArgs()) 373 | of LBN_SETFOCUS: 374 | if this.onGotFocus != nil: this.onGotFocus(this, newEventArgs()) 375 | of LBN_SELCANCEL: 376 | if this.onSelectionCancelled != nil: this.onSelectionCancelled(this, newEventArgs()) 377 | else: discard 378 | 379 | else: return DefSubclassProc(hw, msg, wpm, lpm) 380 | return DefSubclassProc(hw, msg, wpm, lpm) 381 | -------------------------------------------------------------------------------- /Nimforms/menu.nim: -------------------------------------------------------------------------------- 1 | 2 | # menu module - Created on 13-Aug-2024 01:45 3 | # NOTE: This file is included in the middle of 'commons.nim' 4 | #================================================================================ 5 | #[=====================================Menu Docs================================================== 6 | MenuBase: 7 | Abstract base type 8 | Properties: 9 | handle : HMENU 10 | font : Font 11 | menus : Table[string, MenuItem] 12 | 13 | MenuBar ref object of MenuBase 14 | Constructor: newMenuBar 15 | Functions: 16 | addItem 17 | createHandle 18 | 19 | MenuItem ref object of MenuBase 20 | Constructor : newMenuItem 21 | Properties: 22 | foreColor : Color 23 | enabled : bool 24 | Functions: 25 | addItem 26 | 27 | Events: 28 | MenuEventHandler type - proc(m: MenuItem, e: EventArgs) 29 | onClick 30 | onPopup 31 | onCloseup 32 | onFocus 33 | ===================================================================================================]# 34 | var staticMenuID : uint32 = 100 # Global static menu id. 35 | 36 | proc newMenuBar*(parent: Form ) : MenuBar {.discardable.} = 37 | new(result) 38 | result.mHandle = CreateMenu() 39 | result.mFormPtr = parent 40 | result.mFont = newFont("Tahoma", 11) 41 | result.mFormPtr.mMenubar = result 42 | result.mFormPtr.mIsMenuUsed = true 43 | result.mMenuGrayBrush = newColor(0xced4da).makeHBRUSH() 44 | result.mMenuGrayCref = newColor(0x979dac).cref 45 | 46 | 47 | proc menuItemDtor(this: MenuItem) # forward declaration 48 | 49 | proc menuBarDtor(this: MenuBar) = 50 | if len(this.mMenus) > 0: 51 | for key, menu in this.mMenus: menu.menuItemDtor() 52 | DestroyMenu(this.mHandle) 53 | DeleteObject(this.mMenuDefBgBrush) 54 | DeleteObject(this.mMenuHotBgBrush) 55 | DeleteObject(this.mMenuFrameBrush) 56 | DeleteObject(this.mMenuGrayBrush ) 57 | this.mFont.finalize() 58 | # echo "MenuBar destroy worked" 59 | 60 | proc handleWmDrawItem(this: MenuBar, lpm: LPARAM) : LRESULT = 61 | var dis = cast[LPDRAWITEMSTRUCT](lpm) 62 | var mi = cast[MenuItem](cast[PVOID](dis.itemData)) 63 | var txtClrRef : COLORREF = mi.mFgColor.cref 64 | 65 | if dis.itemState == 320 or dis.itemState == 257: 66 | # Mouse is over the menu. Check for enable state. 67 | if mi.mIsEnabled: 68 | let rcbot: int32 = (if mi.mType == mtBaseMenu: dis.rcItem.bottom else: dis.rcItem.bottom - 2) 69 | let rctop: int32 = (if mi.mType == mtBaseMenu: dis.rcItem.top + 1 else: dis.rcItem.top + 2) 70 | let rc = RECT( left: dis.rcItem.left + 4, 71 | top: rctop, 72 | right: dis.rcItem.right, 73 | bottom: rcbot ) 74 | FillRect(dis.hDC, rc.unsafeAddr, this.mMenuHotBgBrush) 75 | FrameRect(dis.hDC, rc.unsafeAddr, this.mMenuFrameBrush) 76 | txtClrRef = 0x00000000 77 | else: 78 | FillRect(dis.hDC, dis.rcItem.unsafeAddr, this.mMenuGrayBrush) 79 | txtClrRef = this.mMenuGrayCref 80 | else: 81 | # Default menu drawing. 82 | FillRect(dis.hDC, dis.rcItem.unsafeAddr, this.mMenuDefBgBrush) 83 | if not mi.mIsEnabled: txtClrRef = this.mMenuGrayCref 84 | 85 | SetBkMode(dis.hDC, 1) 86 | if mi.mType == mtBaseMenu: 87 | dis.rcItem.left += 10 88 | else: 89 | dis.rcItem.left += 25 90 | SelectObject(dis.hDC, this.mFont.handle) 91 | SetTextColor(dis.hDC, txtClrRef) 92 | DrawTextW(dis.hDC, &mi.mWideText, -1, dis.rcItem.unsafeAddr, DT_LEFT or DT_SINGLELINE or DT_VCENTER) 93 | return 0 94 | 95 | 96 | proc newMenuItem*(txt: string, mtyp: MenuType, parentHmenu : HMENU, indexNum: uint32): MenuItem = 97 | new(result) 98 | if mtyp == mtSeparator: 99 | result.mType = mtyp 100 | result.mParentHandle = parentHmenu 101 | else: 102 | # echo "new menu : ", txt 103 | result.mPopup = if mtyp == mtBaseMenu or mtyp == mtPopup: true else: false 104 | result.mHandle = if result.mPopup : CreatePopupMenu() else: CreateMenu() 105 | result.mIndex = indexNum 106 | result.mId = staticMenuID 107 | result.mText = txt 108 | result.mWideText = newWideString(result.mText) 109 | result.mType = mtyp 110 | result.mParentHandle = parentHmenu 111 | result.mBgColor = newColor(0xe9ecef) 112 | result.mFgColor = newColor(0x000000) 113 | result.mIsEnabled = true 114 | 115 | staticMenuID += 1 116 | 117 | proc getTextSize(this: MenuItem, hw: HWND) = 118 | var hdc = GetDC(hw) 119 | GetTextExtentPoint32(hdc, &this.mWideText, this.mWideText.wcLen, 120 | this.mTxtSize.unsafeAddr) 121 | ReleaseDC(hw, hdc) 122 | this.mTxtSizeReady = true 123 | 124 | proc menuItemDtor(this: MenuItem) = 125 | if len(this.mMenus) > 0: 126 | for key, menu in this.mMenus: menu.menuItemDtor() 127 | DestroyMenu(this.mHandle) 128 | # echo "MenuItem destroy worked" 129 | 130 | 131 | proc handleWMMeasureItem(this: MenuItem, pmi: LPMEASUREITEMSTRUCT, hw: HWND) : LRESULT = 132 | if not this.mTxtSizeReady: this.getTextSize(hw) 133 | if this.mType == mtBaseMenu: 134 | pmi.itemWidth = UINT(this.mTxtSize.cx) #+ 10 135 | pmi.itemHeight = UINT(this.mTxtSize.cy) 136 | else: 137 | pmi.itemWidth = 140 #size.cx #+ 10 138 | pmi.itemHeight = 25 139 | return 1 140 | 141 | 142 | proc insertMenuInternal(this: MenuItem, parentHmenu: HMENU) = 143 | var mii : MENUITEMINFOW 144 | mii.cbSize = cast[UINT](mii.sizeof) 145 | mii.fMask = MIIM_ID or MIIM_TYPE or MIIM_DATA or MIIM_SUBMENU or MIIM_STATE 146 | mii.fType = MF_OWNERDRAW 147 | mii.dwTypeData = &this.mWideText 148 | mii.cch = cast[UINT](this.mWideText.wcLen) 149 | mii.dwItemData = cast[ULONG_PTR](cast[PVOID](this)) 150 | mii.wID = cast[UINT](this.mId) 151 | mii.hSubMenu = if this.mPopup : this.mHandle else: nil 152 | InsertMenuItemW(parentHmenu, UINT(this.mIndex), 1, mii.unsafeAddr) 153 | this.mIsCreated = true 154 | # echo "insert menu : ", this.mText, ", popup : ", this.mPopup 155 | 156 | 157 | proc create(this: MenuItem) = 158 | case this.mType 159 | of mtBaseMenu, mtPopup: 160 | if len(this.mMenus) > 0: 161 | for key, menu in this.mMenus: menu.create() 162 | 163 | this.insertMenuInternal(this.mParentHandle) 164 | 165 | of mtMenuItem: 166 | this.insertMenuInternal(this.mParentHandle) 167 | of mtSeparator: 168 | AppendMenuW(this.mParentHandle, MF_SEPARATOR, 0, nil) 169 | else: discard 170 | 171 | 172 | proc addItem*(this: MenuBar, txt: string, txtColor: uint = 0x000000): MenuItem {.discardable.} = 173 | result = newMenuItem(txt, mtBaseMenu, this.mHandle, this.mMenuCount) 174 | result.mFormHwnd = this.mFormPtr.mHandle 175 | result.mFgColor = newColor(txtColor) 176 | result.mFormMenu = true 177 | result.mBar = this 178 | this.mMenuCount += 1 179 | this.mMenus[txt] = result 180 | this.mFormPtr.mMenuItemDict[result.mId] = result 181 | 182 | 183 | proc addItems*(this: MenuBar, args: varargs[string, `$`]) = 184 | for item in args: 185 | let typ : MenuType = (if item == "|": mtSeparator else: mtBaseMenu) 186 | var mi = newMenuItem(item, typ, this.mHandle, this.mMenuCount) 187 | mi.mFormHwnd = this.mFormPtr.mHandle 188 | mi.mFgColor = newColor(0x000000) 189 | mi.mFormMenu = true 190 | mi.mBar = this 191 | this.mMenuCount += 1 192 | this.mMenus[item] = mi 193 | this.mFormPtr.mMenuItemDict[mi.mId] = mi 194 | # echo "Name: ", mi.mText, ", type: ", mi.mType 195 | 196 | 197 | proc addItem*(this: MenuItem, txt: string, txtColor: uint = 0x000000) : MenuItem {.discardable.} = 198 | if this.mType == mtMenuItem: 199 | this.mHandle = CreatePopupMenu() 200 | this.mPopup = true 201 | 202 | if this.mFormMenu: 203 | let mtyp : MenuType = (if txt == "|": mtSeparator else: mtMenuItem) 204 | result = newMenuItem(txt, mtyp, this.mHandle, this.mMenuCount) 205 | result.mFgColor = newColor(txtColor) 206 | result.mFormHwnd = this.mFormHwnd 207 | result.mFormMenu = this.mFormMenu 208 | result.mBar = this.mBar 209 | if this.mType != mtBaseMenu: this.mType = mtPopup 210 | this.mBar.mFormPtr.mMenuItemDict[result.mId] = result # Put new item in form's dict. 211 | else: 212 | raise newException(Exception, "Proc 'addItem' is only supporting menus in Menubar") 213 | 214 | this.mMenuCount += 1 215 | this.mMenus[txt] = result # Put new item in parent menu's dict 216 | 217 | 218 | proc addItems*(this: MenuItem, args: varargs[string, `$`]) = 219 | if this.mType == mtMenuItem: 220 | this.mHandle = CreatePopupMenu() 221 | this.mPopup = true 222 | 223 | for item in args: 224 | let mtyp : MenuType = (if item == "|": mtSeparator else: mtMenuItem) 225 | var mi = newMenuItem(item, mtyp, this.mHandle, this.mMenuCount) 226 | mi.mFgColor = newColor(0x000000) 227 | mi.mFormHwnd = this.mFormHwnd 228 | mi.mFormMenu = this.mFormMenu 229 | mi.mBar = this.mBar 230 | if this.mType != mtBaseMenu: this.mType = mtPopup 231 | this.mMenuCount += 1 232 | this.mMenus[item] = mi # Put new item in parent menu's dict 233 | this.mBar.mFormPtr.mMenuItemDict[mi.mId] = mi # Put new item in form's dict. 234 | 235 | 236 | proc createHandle*(this: MenuBar) = 237 | this.mMenuDefBgBrush = newColor(0xe9ecef).makeHBRUSH() 238 | this.mMenuHotBgBrush = newColor(0x90e0ef).makeHBRUSH() 239 | this.mMenuFrameBrush = newColor(0x0077b6).makeHBRUSH() 240 | if this.mFont.handle == nil: this.mFont.createHandle() 241 | if len(this.mMenus) > 0: 242 | for key, menu in this.mMenus: menu.create() 243 | 244 | SetMenu(this.mFormPtr.mHandle, this.mHandle) 245 | 246 | 247 | proc getChildFromIndex(this: MenuItem, index: uint32): MenuItem = 248 | for key, menu in this.mMenus: 249 | if menu.mIndex == index: 250 | return menu 251 | return nil 252 | 253 | 254 | proc menus*(this: MenuBar): Table[string, MenuItem] = this.mMenus 255 | proc menus*(this: MenuItem): Table[string, MenuItem] = this.mMenus 256 | 257 | # Without this overload, we can't use the index operator on 'menus' proc. 258 | proc `[]`*(this: Table[string, MenuItem], key: string): MenuItem = tables.`[]`(this, key) 259 | 260 | proc foreColor*(this : MenuItem): Color = this.mFgColor 261 | 262 | proc `foreColor=`*(this: MenuItem, value: uint) = 263 | this.mFgColor = newColor(value) 264 | if this.mType == mtBaseMenu: InvalidateRect(this.mHandle, nil, 0) 265 | 266 | proc `foreColor=`*(this: MenuItem, value: Color) = 267 | this.mFgColor = value 268 | if this.mType == mtBaseMenu: InvalidateRect(this.mHandle, nil, 0) 269 | 270 | 271 | proc enabled*(this: MenuItem): bool = this.mIsEnabled 272 | 273 | proc `enabled=`*(this: MenuItem, value: bool) = 274 | this.mIsEnabled = value 275 | if this.mType == mtBaseMenu: InvalidateRect(this.mHandle, nil, 0) 276 | 277 | proc font*(this: MenuItem): Font = this.mFont 278 | 279 | proc `font=`*(this: MenuItem, value: Font) = 280 | this.mFont = value 281 | if this.mType == mtBaseMenu: InvalidateRect(this.mHandle, nil, 0) -------------------------------------------------------------------------------- /Nimforms/nimforms.nim: -------------------------------------------------------------------------------- 1 | # Import this module to start using Nimforms library 2 | 3 | # Essential includes 4 | include apimodule 5 | include winmessages 6 | include typemodule 7 | include colors 8 | include widestring 9 | include graphics 10 | include font 11 | include commons # 'menu.nim' is included in this file. 12 | include events 13 | include controls # contextmenu.nim is included in this file. 14 | include forms 15 | 16 | # Optional includes--------------- 17 | include button 18 | include calendar 19 | include checkbox 20 | include combobox 21 | include datetimepicker 22 | include groupbox 23 | include label 24 | include listbox 25 | include listview 26 | include numberpicker 27 | include progressbar 28 | include radiobutton 29 | include textbox 30 | include trackbar 31 | include treeview 32 | include dialogs 33 | include trayicon 34 | 35 | 36 | -------------------------------------------------------------------------------- /Nimforms/progressbar.nim: -------------------------------------------------------------------------------- 1 | # progressbar module Created on 09-Apr-2023 01:42 AM; Author kcvinker 2 | #[==============================================ProgressBar type============================================== 3 | Constructor - newProgressBar 4 | Functions: 5 | createHandle() - Create the handle of progressBar 6 | increment*() 7 | startMarquee*() 8 | stopMarquee*() 9 | 10 | Properties: 11 | All props inherited from Control type 12 | value : int32 13 | step : int32 14 | style : ProgressBarStyle - {pbsBlock, pbsMarquee} 15 | state : ProgressBarState - {pbsNone, pbsNormal, pbsError, pbsPaused} 16 | marqueeSpeed : int32 17 | showPercentage : bool 18 | 19 | Events: 20 | EventHandler type - proc(c: Control, e: EventArgs) 21 | onProgressChanged 22 | ==========================================================================================================]# 23 | 24 | # Constants 25 | const 26 | PBS_SMOOTH = 0x01 27 | PBS_VERTICAL = 0x04 28 | PBS_MARQUEE = 0x08 29 | PBST_NORMAL = 0x0001 30 | PBST_ERROR = 0x0002 31 | PBST_PAUSED = 0x0003 32 | PBM_SETPOS = (WM_USER+2) 33 | PBM_SETSTEP = (WM_USER+4) 34 | PBM_STEPIT = (WM_USER+5) 35 | PBM_SETRANGE32 = (WM_USER+6) 36 | PBM_GETPOS = (WM_USER+8) 37 | PBM_SETMARQUEE = (WM_USER+10) 38 | PBM_SETSTATE = (WM_USER+16) 39 | 40 | var pbCount = 1 41 | # let pgbClsName = toWcharPtr("msctls_progress32") 42 | let pgbClsName : array[18, uint16] = [0x6D, 0x73, 0x63, 0x74, 0x6C, 0x73, 0x5F, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x65, 0x73, 0x73, 0x33, 0x32, 0] 43 | 44 | 45 | # Forward declaration 46 | proc pbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 47 | proc createHandle*(this: ProgressBar) 48 | # ProgressBar constructor 49 | proc progressBarCtor(parent: Form, x, y, w, h: int32): ProgressBar = 50 | new(result) 51 | result.mKind = ctProgressBar 52 | result.mClassName = cast[LPCWSTR](pgbClsName[0].addr) 53 | result.mName = "ProgressBar_" & $pbCount 54 | result.mParent = parent 55 | result.mXpos = x 56 | result.mYpos = y 57 | result.mWidth = w 58 | result.mHeight = h 59 | # result.mFont = parent.mFont 60 | result.cloneParentFont() 61 | result.mHasFont = true 62 | result.mBackColor = parent.mBackColor 63 | result.mForeColor = CLR_BLACK 64 | result.mStyle = WS_VISIBLE or WS_CHILD 65 | result.mExStyle = 0 66 | result.mMinValue = 0 67 | result.mMaxValue = 100 68 | result.mStep = 1 69 | result.mBarState = pbsNormal 70 | result.mBarStyle = pbsBlock 71 | result.mMarqueeSpeed = 30 72 | pbCount += 1 73 | parent.mControls.add(result) 74 | 75 | 76 | 77 | proc newProgressBar*(parent: Form, x, y: int32, w: int32 = 200, h: int32 = 25, autoc = false, perc = false): ProgressBar = 78 | result = progressBarCtor(parent, x, y, w, h) 79 | result.mShowPerc = perc 80 | if parent.mCreateChilds: result.createHandle() 81 | 82 | 83 | proc setPbStyle(this: ProgressBar) = 84 | if this.mBarStyle == pbsMarquee: this.mStyle = this.mStyle or PBS_MARQUEE 85 | if this.mVertical: this.mStyle = this.mStyle or PBS_VERTICAL 86 | # this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 87 | 88 | 89 | # Create ProgressBar's hwnd 90 | proc createHandle*(this: ProgressBar) = 91 | this.setPbStyle() 92 | this.createHandleInternal() 93 | if this.mHandle != nil: 94 | this.setSubclass(pbWndProc) 95 | this.setFontInternal() 96 | this.sendMsg(PBM_SETRANGE32, this.mMinValue, this.mMaxValue) 97 | this.sendMsg(PBM_SETSTEP, this.mStep, 0) 98 | 99 | method autoCreate(this: ProgressBar) = this.createHandle() 100 | 101 | # Increment progress bar value by step value 102 | proc increment*(this: ProgressBar) = 103 | if this.mIsCreated: 104 | this.mValue = (if this.mValue == this.mMaxValue: this.mStep else: this.mValue + this.mStep) 105 | this.sendMsg(PBM_STEPIT, 0, 0) 106 | 107 | # Start the marquee animation 108 | proc startMarquee*(this: ProgressBar) = 109 | if this.mIsCreated and this.mBarStyle == pbsMarquee: 110 | this.sendMsg(PBM_SETMARQUEE, 1, this.mMarqueeSpeed) 111 | 112 | # Stop the marquee animation 113 | proc stopMarquee*(this: ProgressBar) = 114 | if this.mIsCreated and this.mBarStyle == pbsMarquee: 115 | this.sendMsg(PBM_SETMARQUEE, 0, this.mMarqueeSpeed) 116 | 117 | 118 | #----ProgressBar properties-------------------------------------------------------------- 119 | 120 | proc `value=`*(this: ProgressBar, value: int32) {.inline.} = 121 | if value >= this.mMinValue and value <= this.mMaxValue: 122 | this.mValue = value 123 | if this.mIsCreated: this.sendMsg(PBM_SETPOS, value, 0) 124 | else: 125 | raise newException(Exception, "value is not in between minValue & maxValue") 126 | 127 | proc value*(this: ProgressBar): int32 {.inline.} = this.mValue 128 | 129 | proc `style=`*(this: ProgressBar, value: ProgressBarStyle) = 130 | if this.mIsCreated and this.mBarStyle != value: 131 | this.mValue = 0 132 | if value == pbsBlock: 133 | this.mStyle = this.mStyle xor PBS_MARQUEE 134 | this.mStyle = this.mStyle or PBS_SMOOTH 135 | else: 136 | this.mStyle = this.mStyle xor PBS_SMOOTH 137 | this.mStyle = this.mStyle or PBS_MARQUEE 138 | SetWindowLongPtr(this.mHandle, GWL_STYLE, cast[LONG_PTR](this.mStyle)) 139 | if value == pbsMarquee: this.sendMsg(PBM_SETMARQUEE, 1, this.mMarqueeSpeed) 140 | this.mBarStyle = value 141 | 142 | proc style*(this: ProgressBar): ProgressBarStyle {.inline.} = this.mBarStyle 143 | 144 | proc `marqueeSpeed=`*(this: ProgressBar, value: int32) {.inline.} = this.mMarqueeSpeed = value 145 | proc marqueeSpeed*(this: ProgressBar): int32 {.inline.} = this.mMarqueeSpeed 146 | 147 | proc `step=`*(this: ProgressBar, value: int32) = 148 | if value >= this.mMinValue and value <= this.mMaxValue: 149 | this.mStep = value 150 | if this.mIsCreated: this.sendMsg(PBM_SETSTEP, this.mStep, 0) 151 | else: 152 | raise newException(Exception, "value is not in between minValue & maxValue") 153 | 154 | proc step*(this: ProgressBar): int32 {.inline.} = this.mStep 155 | 156 | proc `state=`*(this: ProgressBar, value: ProgressBarState) {.inline.} = 157 | this.mBarState = value 158 | if this.mIsCreated: this.sendMsg(PBM_SETSTATE, value, 0) 159 | 160 | proc state*(this: ProgressBar): ProgressBarState {.inline.} = this.mBarState 161 | 162 | proc `showPercentage=`*(this: ProgressBar, value: bool) {.inline.} = this.mShowPerc = value 163 | proc showPercentage*(this: ProgressBar): bool {.inline.} = this.mShowPerc 164 | 165 | 166 | 167 | 168 | proc pbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 169 | 170 | case msg 171 | of WM_DESTROY: 172 | RemoveWindowSubclass(hw, pbWndProc, scID) 173 | var this = cast[ProgressBar](refData) 174 | this.destructor() 175 | 176 | of WM_LBUTTONDOWN: 177 | var this = cast[ProgressBar](refData) 178 | this.leftButtonDownHandler(msg, wpm, lpm) 179 | of WM_LBUTTONUP: 180 | var this = cast[ProgressBar](refData) 181 | this.leftButtonUpHandler(msg, wpm, lpm) 182 | of WM_RBUTTONDOWN: 183 | var this = cast[ProgressBar](refData) 184 | this.rightButtonDownHandler(msg, wpm, lpm) 185 | of WM_RBUTTONUP: 186 | var this = cast[ProgressBar](refData) 187 | this.rightButtonUpHandler(msg, wpm, lpm) 188 | of WM_MOUSEMOVE: 189 | var this = cast[ProgressBar](refData) 190 | this.mouseMoveHandler(msg, wpm, lpm) 191 | of WM_MOUSELEAVE: 192 | var this = cast[ProgressBar](refData) 193 | this.mouseLeaveHandler() 194 | of WM_CONTEXTMENU: 195 | var this = cast[ProgressBar](refData) 196 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 197 | 198 | of WM_PAINT: 199 | var this = cast[ProgressBar](refData) 200 | if this.mShowPerc and this.mBarStyle == pbsBlock: 201 | discard DefSubclassProc(hw, msg, wpm, lpm) 202 | var ss: SIZE 203 | let vtext = $this.mValue & "%" 204 | let wtext = vtext.toWcharPtr() 205 | var hdc: HDC = GetDC(hw) 206 | SelectObject(hdc, this.mFont.handle) 207 | GetTextExtentPoint32(hdc, wtext, int32(vtext.len), ss.unsafeAddr) 208 | let x = int32((this.width - ss.cx) / 2) 209 | let y = int32((this.height - ss.cy) / 2) 210 | SetBkMode(hdc, 1) 211 | SetTextColor(hdc, this.mForeColor.cref) 212 | TextOut(hdc, x, y, wtext, int32(vtext.len)) 213 | ReleaseDC(hw, hdc) 214 | return 0 215 | else: 216 | return DefSubclassProc(hw, msg, wpm, lpm) 217 | 218 | else: return DefSubclassProc(hw, msg, wpm, lpm) 219 | return DefSubclassProc(hw, msg, wpm, lpm) 220 | -------------------------------------------------------------------------------- /Nimforms/propertygrid.nim: -------------------------------------------------------------------------------- 1 | # PropertyGrid module [WIP!!!] 2 | # Created on 01-Jun-2023 08:36 AM; Author kcvinker 3 | 4 | var pgridCount = 1 5 | proc pGridWndProc( hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM): LRESULT {.stdcall.} 6 | proc registerPGClass(this: PropertyGrid) = 7 | var wcex : WNDCLASSEXW 8 | wcex.cbSize = cast[UINT](sizeof(wcex)) 9 | wcex.style = CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS 10 | wcex.lpfnWndProc = pGridWndProc 11 | wcex.cbClsExtra = 0 12 | wcex.cbWndExtra = 0 13 | wcex.hInstance = this.mHInst 14 | wcex.hCursor = LoadCursorW(ZERO_HINST, cast[LPCWSTR](IDC_ARROW)) 15 | wcex.hbrBackground = CreateSolidBrush(this.mBackColor.cref) # 16 | wcex.lpszClassName = this.mWClsNamePtr 17 | var ret = RegisterClassEx(wcex.unsafeAddr) 18 | this.mRegistered = true 19 | 20 | 21 | proc newPropertyGrid(parent: Form, x, y: int32 = 25, w: int32 = 150, h: int32 = 300) : PropertyGrid = 22 | new(result) 23 | result.mClassName = "Nimforms_PropertyGrid" 24 | result.mHInst = parent.hInstance 25 | this.mBackColor = newColor(0xFFFFFF) 26 | this.mWClsNamePtr = toWcharPtr(this.mClassName) 27 | if not this.mRegistered: this.registerPGClass() 28 | result.mName = "PropertyGrid_" & $pgridCount 29 | result.mParent = parent 30 | result.mXpos = x 31 | result.mYpos = y 32 | result.mWidth = w 33 | result.mHeight = h 34 | result.mFont = parent.mFont 35 | pgridCount += 1 36 | 37 | proc destroyPropGrid(this: PropertyGrid) = 38 | pgridCount -= 1 39 | this.destructor() # Call base destructor 40 | DestroyWindow(this.mHandle) 41 | if pgridCount == 0: 42 | UnregisterClass(this.mWClsNamePtr, this.mHInst) 43 | 44 | 45 | 46 | proc mainWndProc( hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM): LRESULT = 47 | var this = cast[PropertyGrid](GetWindowLongPtrW(hw, GWLP_USERDATA)) 48 | case msg 49 | of WM_DESTROY: 50 | this.destroyPropGrid() 51 | # PostQuitMessage(0) 52 | 53 | 54 | else: return DefWindowProcW(hw, msg, wpm, lpm) 55 | return DefWindowProcW(hw, msg, wpm, lpm) -------------------------------------------------------------------------------- /Nimforms/radiobutton.nim: -------------------------------------------------------------------------------- 1 | # radiobutton module Created on 04-Apr-2023 02:34 AM; Author kcvinker 2 | #[==========================================RadioButton Docs========================================================= 3 | Constructor - newRadioButton 4 | Functions: 5 | createHandle() - Create the handle of radioButton 6 | 7 | Properties: 8 | All props inherited from Control type 9 | checked bool 10 | 11 | Events: 12 | All events inherited from Control type 13 | EventHandler type - proc(c: Control, e: EventArgs) 14 | onCheckedChanged 15 | =========================================================================================================]# 16 | 17 | var rbCount = 1 18 | # let rbClsName = toWcharPtr("Button") 19 | 20 | # Forward declaration 21 | proc rbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 22 | proc createHandle*(this: RadioButton) 23 | # RadioButton constructor 24 | proc newRadioButton*(parent: Form, text: string, x: int32 = 10, y: int32 = 10, w: int32 = 0, h: int32 = 0): RadioButton = 25 | new(result) 26 | result.mKind = ctRadioButton 27 | result.mClassName = cast[LPCWSTR](BtnClass[0].addr) 28 | result.mName = "RadioButton_" & $rbCount 29 | result.mParent = parent 30 | result.mXpos = x 31 | result.mYpos = y 32 | result.mWidth = w 33 | result.mHeight = h 34 | result.mText = text 35 | # result.mFont = parent.mFont 36 | result.cloneParentFont() 37 | result.mHasFont = true 38 | result.mBackColor = parent.mBackColor 39 | result.mWideText = text.toLPWSTR() 40 | result.mForeColor = CLR_BLACK 41 | result.mAutoSize = true 42 | result.mTxtFlag = DT_SINGLELINE or DT_VCENTER 43 | result.mStyle = WS_CHILD or WS_VISIBLE or WS_TABSTOP or BS_AUTORADIOBUTTON 44 | result.mExStyle = WS_EX_LTRREADING or WS_EX_LEFT 45 | # result.mTextStyle = DT_SINGLELINE or DT_VCENTER 46 | rbCount += 1 47 | parent.mControls.add(result) 48 | if parent.mCreateChilds: result.createHandle() 49 | 50 | proc setRBStyle(this: RadioButton) = 51 | if this.mRightAlign: 52 | this.mStyle = this.mStyle or BS_RIGHTBUTTON 53 | this.mTxtFlag = this.mTxtFlag or DT_RIGHT 54 | this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 55 | 56 | 57 | # Create RadioButton's hwnd 58 | proc createHandle*(this: RadioButton) = 59 | this.setRBStyle() 60 | this.createHandleInternal() 61 | if this.mHandle != nil: 62 | this.setSubclass(rbWndProc) 63 | this.setFontInternal() 64 | this.setIdealSize() 65 | if this.mChecked: this.sendMsg(BM_SETCHECK, 1, 0) 66 | 67 | method autoCreate(this: RadioButton) = this.createHandle() 68 | 69 | # # Set the checked property 70 | proc `checked=`*(this: RadioButton, value: bool) {.inline.} = 71 | this.mChecked = value 72 | if this.mIsCreated: this.sendMsg(BM_SETCHECK, int32(value), 0) 73 | 74 | # # Get the checked property 75 | proc checked*(this: RadioButton): bool {.inline.} = this.mChecked 76 | 77 | 78 | proc rbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 79 | 80 | case msg 81 | of WM_DESTROY: 82 | RemoveWindowSubclass(hw, rbWndProc, scID) 83 | var this = cast[RadioButton](refData) 84 | this.destructor() 85 | 86 | of WM_LBUTTONDOWN: 87 | var this = cast[RadioButton](refData) 88 | this.leftButtonDownHandler(msg, wpm, lpm) 89 | 90 | of WM_LBUTTONUP: 91 | var this = cast[RadioButton](refData) 92 | this.leftButtonUpHandler(msg, wpm, lpm) 93 | 94 | of WM_RBUTTONDOWN: 95 | var this = cast[RadioButton](refData) 96 | this.rightButtonDownHandler(msg, wpm, lpm) 97 | 98 | of WM_RBUTTONUP: 99 | var this = cast[RadioButton](refData) 100 | this.rightButtonUpHandler(msg, wpm, lpm) 101 | 102 | of WM_MOUSEMOVE: 103 | var this = cast[RadioButton](refData) 104 | this.mouseMoveHandler(msg, wpm, lpm) 105 | 106 | of WM_MOUSELEAVE: 107 | var this = cast[RadioButton](refData) 108 | this.mouseLeaveHandler() 109 | 110 | of WM_CONTEXTMENU: 111 | var this = cast[RadioButton](refData) 112 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 113 | 114 | of MM_LABEL_COLOR: 115 | var this = cast[RadioButton](refData) 116 | let hdc = cast[HDC](wpm) 117 | SetBkMode(hdc, 1) 118 | if (this.mDrawMode and 2) == 2: SetBkColor(hdc, this.mBackColor.cref) 119 | return cast[LRESULT](this.mBkBrush) 120 | 121 | of MM_CTL_COMMAND: 122 | var this = cast[RadioButton](refData) 123 | this.mChecked = bool(this.sendMsg(BM_GETCHECK, 0, 0)) 124 | if this.onCheckedChanged != nil: this.onCheckedChanged(this, newEventArgs()) 125 | 126 | of MM_NOTIFY_REFLECT: 127 | var this = cast[RadioButton](refData) 128 | # We use this message only to change the fore color. 129 | let nmcd = cast[LPNMCUSTOMDRAW](lpm) 130 | case nmcd.dwDrawStage 131 | of CDDS_PREERASE: return CDRF_NOTIFYPOSTERASE 132 | of CDDS_PREPAINT: 133 | if not this.mRightAlign: 134 | nmcd.rc.left += 18 135 | else: 136 | nmcd.rc.right -= 18 137 | 138 | if (this.mDrawMode and 1) == 1: SetTextColor(nmcd.hdc, this.mForeColor.cref) 139 | # SetBkMode(nmcd.hdc, 1) 140 | DrawTextW(nmcd.hdc, this.mWideText, -1, nmcd.rc.unsafeAddr, this.mTxtFlag) 141 | return CDRF_SKIPDEFAULT 142 | else: discard 143 | return 0 144 | 145 | else: return DefSubclassProc(hw, msg, wpm, lpm) 146 | return DefSubclassProc(hw, msg, wpm, lpm) 147 | -------------------------------------------------------------------------------- /Nimforms/textbox.nim: -------------------------------------------------------------------------------- 1 | # textbox module Created on 04-Apr-2023 03:44 AM; Author kcvinker 2 | #[=================================================TextBox Docs=============================================== 3 | Constructor - newTextBox 4 | Functions 5 | createHandle() - Create the handle of textBox 6 | 7 | Properties: 8 | All props inherited from Control type 9 | textAlign TextAlignment - {taLeft, taCenter, taRight} 10 | textCase TextCase - {tcNormal, tcLowerCase, tcUpperCase} 11 | textType TextType - {ttNormal, ttNumberOnly, ttPasswordChar} 12 | cueBanner string 13 | multiLine bool 14 | hideSelection bool 15 | readOnly bool 16 | 17 | Events 18 | All events inherited from Control type 19 | EventHandler type - proc(c: Control, e: EventArgs) 20 | onTextChanged 21 | =========================================================================================================]# 22 | # Constants 23 | const 24 | ECM_FIRST = 0x1500 25 | ES_AUTOHSCROLL = 128 26 | ES_MULTILINE = 4 27 | ES_WANTRETURN = 4096 28 | ES_NOHIDESEL = 256 29 | ES_READONLY = 0x800 30 | ES_LOWERCASE = 16 31 | ES_UPPERCASE = 8 32 | ES_PASSWORD = 32 33 | EM_SETCUEBANNER = ECM_FIRST + 1 34 | EN_CHANGE = 0x0300 35 | 36 | var tbCount = 1 37 | # let tbClsName = toWcharPtr("Edit") 38 | let tbClsName : array[5, uint16] = [0x45, 0x64, 0x69, 0x74, 0] 39 | 40 | 41 | let TBSTYLE : DWORD = WS_CHILD or WS_VISIBLE or ES_LEFT or WS_TABSTOP or ES_AUTOHSCROLL or WS_MAXIMIZEBOX or WS_OVERLAPPED 42 | let TBEXSTYLE: DWORD = WS_EX_LEFT or WS_EX_LTRREADING or WS_EX_CLIENTEDGE or WS_EX_NOPARENTNOTIFY 43 | 44 | # Forward declaration 45 | proc tbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} 46 | proc createHandle*(this: TextBox) 47 | # TextBox constructor 48 | proc newTextBox*(parent: Form, text: string = "", x: int32 = 10, y: int32 = 10, w: int32 = 120, h: int32 = 27): TextBox = 49 | new(result) 50 | result.mKind = ctTextBox 51 | result.mClassName = cast[LPCWSTR](tbClsName[0].addr) 52 | result.mName = "TextBox_" & $tbCount 53 | result.mParent = parent 54 | result.mXpos = x 55 | result.mYpos = y 56 | result.mWidth = w 57 | result.mHeight = h 58 | result.mText = text 59 | # result.mFont = parent.mFont 60 | result.cloneParentFont() 61 | result.mHasFont = true 62 | result.mBackColor = CLR_WHITE 63 | # result.mTxtFlag = DT_SINGLELINE or DT_VCENTER 64 | result.mForeColor = CLR_BLACK 65 | result.mStyle = TBSTYLE 66 | result.mExStyle = TBEXSTYLE 67 | tbCount += 1 68 | parent.mControls.add(result) 69 | if parent.mCreateChilds: result.createHandle() 70 | 71 | proc setTBStyle(this: TextBox) = 72 | if this.mMultiLine: this.mStyle = this.mStyle or ES_MULTILINE or ES_WANTRETURN 73 | if not this.mHideSel: this.mStyle = this.mStyle or ES_NOHIDESEL 74 | if this.mReadOnly: this.mStyle = this.mStyle or ES_READONLY 75 | 76 | if this.mTextCase == tcLowerCase: 77 | this.mStyle = this.mStyle or ES_LOWERCASE 78 | elif this.mTextCase == tcUpperCase: 79 | this.mStyle = this.mStyle or ES_UPPERCASE 80 | 81 | if this.mTextType == ttNumberOnly: 82 | this.mStyle = this.mStyle or ES_NUMBER 83 | elif this.mTextType == ttPasswordChar: 84 | this.mStyle = this.mStyle or ES_PASSWORD 85 | 86 | if this.mTextAlign == taCenter: 87 | this.mStyle = this.mStyle or ES_CENTER 88 | elif this.mTextAlign == taRight: 89 | this.mStyle = this.mStyle or ES_RIGHT 90 | this.mBkBrush = CreateSolidBrush(this.mBackColor.cref) 91 | 92 | # Create TextBox's hwnd 93 | proc createHandle*(this: TextBox) = 94 | this.setTBStyle() 95 | this.createHandleInternal() 96 | if this.mHandle != nil: 97 | this.setSubclass(tbWndProc) 98 | this.setFontInternal() 99 | if this.mCueBanner.len > 0: this.sendMsg(EM_SETCUEBANNER, 1, toWcharPtr(this.mCueBanner)) 100 | 101 | method autoCreate(this: TextBox) = this.createHandle() 102 | # Properties-------------------------------------------------------------------------- 103 | 104 | proc `textAlign=`*(this: TextBox, value: TextAlignment) = this.mTextAlign = value 105 | proc textAlign*(this: TextBox): TextAlignment = this.mTextAlign 106 | 107 | proc `textCase=`*(this: TextBox, value: TextCase) = this.mTextCase = value 108 | proc textCase*(this: TextBox): TextCase = this.mTextCase 109 | 110 | proc `textType=`*(this: TextBox, value: TextType) = this.mTextType = value 111 | proc textType*(this: TextBox): TextType = this.mTextType 112 | 113 | proc `cueBanner=`*(this: TextBox, value: string) = this.mCueBanner = value 114 | proc cueBanner*(this: TextBox): string = this.mCueBanner 115 | 116 | proc `multiLine=`*(this: TextBox, value: bool) = this.mMultiLine = value 117 | proc multiLine*(this: TextBox): bool = this.mMultiLine 118 | 119 | proc `hideSelection=`*(this: TextBox, value: bool) = this.mHideSel = value 120 | proc hideSelection*(this: TextBox): bool = this.mHideSel 121 | 122 | proc `readOnly=`*(this: TextBox, value: bool) = this.mReadOnly = value 123 | proc readOnly*(this: TextBox): bool = this.mReadOnly 124 | 125 | 126 | 127 | 128 | proc tbWndProc(hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM, scID: UINT_PTR, refData: DWORD_PTR): LRESULT {.stdcall.} = 129 | 130 | case msg 131 | of WM_DESTROY: 132 | RemoveWindowSubclass(hw, tbWndProc, scID) 133 | var this = cast[TextBox](refData) 134 | this.destructor() 135 | 136 | of WM_LBUTTONDOWN: 137 | var this = cast[TextBox](refData) 138 | this.leftButtonDownHandler(msg, wpm, lpm) 139 | 140 | of WM_LBUTTONUP: 141 | var this = cast[TextBox](refData) 142 | this.leftButtonUpHandler(msg, wpm, lpm) 143 | 144 | of WM_RBUTTONDOWN: 145 | var this = cast[TextBox](refData) 146 | this.rightButtonDownHandler(msg, wpm, lpm) 147 | 148 | of WM_RBUTTONUP: 149 | var this = cast[TextBox](refData) 150 | this.rightButtonUpHandler(msg, wpm, lpm) 151 | 152 | of WM_MOUSEMOVE: 153 | var this = cast[TextBox](refData) 154 | this.mouseMoveHandler(msg, wpm, lpm) 155 | 156 | of WM_MOUSELEAVE: 157 | var this = cast[TextBox](refData) 158 | this.mouseLeaveHandler() 159 | 160 | of WM_KEYDOWN: 161 | var this = cast[TextBox](refData) 162 | this.keyDownHandler(wpm) 163 | 164 | of WM_KEYUP: 165 | var this = cast[TextBox](refData) 166 | this.keyUpHandler(wpm) 167 | 168 | of WM_CHAR: 169 | var this = cast[TextBox](refData) 170 | this.keyPressHandler(wpm) 171 | 172 | of WM_CONTEXTMENU: 173 | var this = cast[TextBox](refData) 174 | if this.mContextMenu != nil: this.mContextMenu.showMenu(lpm) 175 | 176 | of MM_EDIT_COLOR: 177 | var this = cast[TextBox](refData) 178 | if this.mDrawMode > 0: 179 | let hdc = cast[HDC](wpm) 180 | if (this.mDrawMode and 1) == 1: SetTextColor(hdc, this.mForeColor.cref) 181 | if (this.mDrawMode and 2) == 2: SetBkColor(hdc, this.mBackColor.cref) 182 | return cast[LRESULT](this.mBkBrush) 183 | 184 | of MM_CTL_COMMAND: 185 | var this = cast[TextBox](refData) 186 | let ncode = HIWORD(wpm) 187 | if ncode == EN_CHANGE: 188 | if this.onTextChanged != nil: this.onTextChanged(this, newEventArgs()) 189 | 190 | else: return DefSubclassProc(hw, msg, wpm, lpm) 191 | return DefSubclassProc(hw, msg, wpm, lpm) 192 | -------------------------------------------------------------------------------- /Nimforms/trayicon.nim: -------------------------------------------------------------------------------- 1 | # menu module - Created on 14-Aug-2024 00:57 2 | #[========================================TrayIcon Docs======================================== 3 | Constructor: newTrayIcon 4 | Functions: 5 | showBalloon 6 | addContextMenu 7 | 8 | Properties: 9 | menuTrigger : TrayMenuTrigger - enum [See typemodule.nim] 10 | tooltip : string 11 | icon : string 12 | 13 | Events: 14 | TrayIconEventHandler type - proc(c: TrayIcon, e: EventArgs) 15 | onBalloonShow 16 | onBalloonClose 17 | onBalloonClick 18 | onMouseMove 19 | onLeftMouseDown 20 | onLeftMouseUp 21 | onRightMouseDown 22 | onRightMouseUp 23 | onLeftClick 24 | onRightClick 25 | onLeftDoubleClick 26 | ===============================================================================================]# 27 | 28 | # Class name - "Tray_Msg_Win" 29 | let trayClsName : array[13, uint16] = [0x54, 0x72, 0x61, 0x79, 0x5F, 0x4D, 0x73, 0x67, 0x5F, 0x57, 0x69, 0x6E, 0] 30 | const LIMG_FLAG = LR_DEFAULTCOLOR or LR_LOADFROMFILE 31 | var trayWinRegistered : bool 32 | proc createTrayMsgWindow(this: TrayIcon) # Forward declaration 33 | proc trayWndProc( hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM): LRESULT {.stdcall.} 34 | 35 | const 36 | # Notify Icon messages 37 | NIM_ADD = 0x00000000 38 | NIM_MODIFY = 0x00000001 39 | NIM_DELETE = 0x00000002 40 | NIM_SETFOCUS = 0x00000003 41 | NIM_SETVERSION = 0x00000004 42 | 43 | # Notify Icon Notifications 44 | NIF_MESSAGE = 0x00000001 45 | NIF_ICON = 0x00000002 46 | NIF_TIP = 0x00000004 47 | NIF_STATE = 0x00000008 48 | NIF_INFO = 0x00000010 49 | NIF_GUID = 0x00000020 50 | NIF_REALTIME = 0x00000040 51 | NIF_SHOWTIP = 0x00000080 52 | 53 | # Notify Icon Constants 54 | NIS_HIDDEN = 0x00000001 55 | NIS_SHAREDICON = 0x00000002 56 | 57 | NIIF_NONE = 0x00000000 58 | NIIF_INFO = 0x00000001 59 | NIIF_WARNING = 0x00000002 60 | NIIF_ERROR = 0x00000003 61 | NIIF_USER = 0x00000004 62 | NIIF_NOSOUND = 0x00000010 63 | NIIF_LARGE_ICON = 0x00000020 64 | NIIF_RESPECT_QUIET_TIME = 0x00000080 65 | NIIF_ICON_MASK = 0x0000000F 66 | NIN_SELECT = (WM_USER + 0) 67 | NINF_KEY = 0x1 68 | NIN_KEYSELECT = (NIN_SELECT or NINF_KEY) 69 | NIN_BALLOONSHOW = (WM_USER + 2) 70 | NIN_BALLOONHIDE = (WM_USER + 3) 71 | NIN_BALLOONTIMEOUT = (WM_USER + 4) 72 | NIN_BALLOONUSERCLICK = (WM_USER + 5) 73 | NIN_POPUPOPEN = (WM_USER + 6) 74 | NIN_POPUPCLOSE = (WM_USER + 7) 75 | 76 | 77 | proc newTrayIcon*(tooltip : string, iconpath : string = ""): TrayIcon = 78 | new(result) 79 | result.mTooltip = tooltip 80 | result.mIconpath = iconpath 81 | result.createTrayMsgWindow() 82 | if iconpath == "": 83 | result.mhTrayIcon = LoadIconW(nil, IDI_SHIELD) 84 | result.mIconpath = "Sys_Icon_Shield" 85 | else: 86 | result.mhTrayIcon = LoadImageW(nil, toWcharPtr(iconpath), IMAGE_ICON, 0, 0, LIMG_FLAG) 87 | if result.mhTrayIcon == nil: 88 | result.mhTrayIcon = LoadIconW(nil, IDI_SHIELD) 89 | result.mIconpath = "Sys_Icon_Shield" 90 | echo "Can't create icon from ", iconpath 91 | #----------------------------------------------- 92 | let tipTxt = newWideCString(tooltip) 93 | result.mNid.cbSize = cast[DWORD](sizeof(result.mNid)) 94 | result.mNid.hWnd = result.mMsgHwnd 95 | result.mNid.uID = 1 96 | result.mNid.uVersionOrTimeout = 4 97 | result.mNid.uFlags = NIF_ICON or NIF_MESSAGE or NIF_TIP 98 | result.mNid.uCallbackMessage = MM_TRAY_MSG 99 | result.mNid.hIcon = result.mhTrayIcon 100 | for i in 0..tipTxt.len: result.mNid.toolTipText[i] = tipTxt[i] 101 | let x = Shell_NotifyIconW(NIM_ADD, &result.mNid) 102 | 103 | 104 | proc trayIconDtor(this: TrayIcon) = 105 | DestroyWindow(this.mMsgHwnd) 106 | 107 | # Properties------------------------------------------ 108 | 109 | proc menuTrigger*(this: TrayIcon): TrayMenuTrigger = this.mMenuTrigger 110 | 111 | proc `menuTrigger=`*(this: TrayIcon, value: TrayMenuTrigger) = 112 | this.mMenuTrigger = value 113 | this.mTrig = cast[uint8](value) 114 | 115 | proc tooltip*(this: TrayIcon): string = this.mToolTip 116 | 117 | proc `tooltip=`*(this: TrayIcon, value: string) = 118 | this.mToolTip = value 119 | this.mNid.uFlags = NIF_ICON or NIF_MESSAGE or NIF_TIP 120 | let tipTxt = newWideCString(value) 121 | for i in 0..tipTxt.len: this.mNid.toolTipText[i] = tipTxt[i] 122 | let x = Shell_NotifyIconW(NIM_MODIFY, &this.mNid) 123 | 124 | 125 | proc icon*(this: TrayIcon): string = this.mIconpath 126 | 127 | proc `icon=`*(this: TrayIcon, value: string) = 128 | this.mIconpath = value 129 | this.mhTrayIcon = LoadImageW(nil, toWcharPtr(value), IMAGE_ICON, 0, 0, LIMG_FLAG) 130 | if this.mhTrayIcon == nil: 131 | this.mhTrayIcon = LoadIconW(nil, IDI_SHIELD) 132 | echo "Can't create icon from ", value 133 | 134 | this.mNid.uFlags = NIF_ICON or NIF_MESSAGE or NIF_TIP 135 | this.mNid.hIcon = this.mhTrayIcon 136 | let x = Shell_NotifyIconW(NIM_MODIFY, &this.mNid) 137 | 138 | proc contextMenu*(this: TrayIcon): ContextMenu = this.mCmenu 139 | # Methods--------------------------------------------------- 140 | 141 | proc showBalloon*(this: TrayIcon, title, message: string, # Balloon title & Balloon text 142 | timeout: uint32, noSound: bool = false, # Balloon timeout in ms, Do you want to play system sound? 143 | icon: BalloonIcon = BalloonIcon.biInfo, # System defined icons, but you can choose custom icon. 144 | iconpath: string = "") = # If you choose custom icon, pass an icon path here. 145 | 146 | let bTitle = newWideCString(title) 147 | let bMsg = newWideCString(message) 148 | this.mNid.uFlags = NIF_ICON or NIF_MESSAGE or NIF_TIP or NIF_INFO 149 | for i in 0..bTitle.len: this.mNid.balloonTitle[i] = bTitle[i] 150 | for i in 0..bMsg.len: this.mNid.balloonText[i] = bMsg[i] 151 | if icon == BalloonIcon.biCustom and iconpath != "": 152 | this.mNid.hIcon = LoadImageW(nil, toWcharPtr(iconpath), IMAGE_ICON, 0, 0, LIMG_FLAG) 153 | if this.mNid.hIcon == nil: 154 | this.mNid.hIcon = this.mhTrayIcon 155 | else: 156 | # We successfully created an icon handle from 'iconpath' parameter. 157 | # So, for this balloon, we will show this icon. But We need to... 158 | # ...reset the old icon after this balloon vanished. 159 | # Otherwise, from now on we need to use this icon in Balloons and tray. 160 | this.mResetIcon = true 161 | #------------------------ 162 | #---------------------------- 163 | this.mNid.dwInfoFlags = cast[DWORD](icon) 164 | this.mNid.uVersionOrTimeout = timeout 165 | if noSound: this.mNid.dwInfoFlags = this.mNid.dwInfoFlags or NIIF_NOSOUND 166 | Shell_NotifyIconW(NIM_MODIFY, &this.mNid) 167 | this.mNid.dwInfoFlags = 0 168 | this.mNid.uFlags = 0 169 | 170 | 171 | proc addContextMenu*(this: TrayIcon, trigger: TrayMenuTrigger, 172 | menuNames: varargs[string, `$`]) : ContextMenu {.discardable.} = 173 | result = newContextMenu(this, menuNames) 174 | this.mCmenu = result 175 | this.mCmenuUsed = true 176 | this.mMenuTrigger = trigger 177 | this.mTrig = cast[uint8](trigger) 178 | 179 | 180 | 181 | proc resetIconInternal(this: TrayIcon) = 182 | this.mNid.uFlags = NIF_ICON or NIF_MESSAGE or NIF_TIP 183 | this.mNid.hIcon = this.mhTrayIcon 184 | Shell_NotifyIconW(NIM_MODIFY, &this.mNid) 185 | this.mResetIcon = false # Revert to the default state 186 | 187 | # proc showContextMenu(this: TrayIcon) = 188 | # var pt : POINT 189 | # if not this.mCmenu.mMenuInserted: this.mCmenu.cmenuCreateHandle() 190 | # GetCursorPos(&pt) 191 | # TrackPopupMenu(this.mCmenu.mHandle, 2, pt.x, pt.y, 0, this.mCmenu.mDummyHwnd, nil) 192 | 193 | 194 | proc createTrayMsgWindow(this: TrayIcon) = 195 | let clsname = cast[LPCWSTR](trayClsName[0].addr) 196 | if not trayWinRegistered: 197 | registerMessageWindowClass(clsname, trayWndProc) 198 | trayWinRegistered = true 199 | 200 | this.mMsgHwnd = CreateWindowExW(0, clsname, nil, 0, 0, 0, 0, 0, 201 | HWND_MESSAGE, nil, appData.hInstance, nil) 202 | if this.mMsgHwnd != nil: 203 | SetWindowLongPtrW(this.mMsgHwnd, GWLP_USERDATA, cast[LONG_PTR](cast[PVOID](this))) 204 | appData.trayHwnd = this.mMsgHwnd 205 | else: 206 | echo "Cannot create dummy window for tray icon, Error: ", GetLastError() 207 | 208 | 209 | 210 | proc trayWndProc( hw: HWND, msg: UINT, wpm: WPARAM, lpm: LPARAM): LRESULT {.stdcall.} = 211 | case msg 212 | of WM_DESTROY: 213 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 214 | Shell_NotifyIconW(NIM_DELETE, &this.mNid) 215 | if this.mhTrayIcon != nil: DestroyIcon(this.mhTrayIcon) 216 | if this.mCmenu != nil: this.mCmenu.cmenuDtor() 217 | appData.trayHwnd = nil 218 | 219 | of MM_TRAY_MSG: 220 | case lpm 221 | of NIN_BALLOONSHOW: 222 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 223 | if this.onBalloonShow != nil: this.onBalloonShow(this, newEventArgs()) 224 | 225 | of NIN_BALLOONTIMEOUT: 226 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 227 | if this.onBalloonClose != nil: this.onBalloonClose(this, newEventArgs()) 228 | if this.mResetIcon: this.resetIconInternal() 229 | 230 | of NIN_BALLOONUSERCLICK: 231 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 232 | if this.onBalloonClick != nil: this.onBalloonClick(this, newEventArgs()) 233 | if this.mResetIcon: this.resetIconInternal() 234 | 235 | of WM_LBUTTONDOWN: 236 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 237 | if this.onLeftMouseDown != nil: this.onLeftMouseDown(this, newEventArgs()) 238 | 239 | of WM_LBUTTONUP: 240 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 241 | if this.onLeftMouseUp != nil: this.onLeftMouseUp(this, newEventArgs()) 242 | if this.onLeftClick != nil: this.onLeftClick(this, newEventArgs()) 243 | if this.mCmenuUsed and (this.mTrig and 1) == 1 : 244 | this.mCmenu.showMenu(0) 245 | 246 | of WM_LBUTTONDBLCLK: 247 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 248 | if this.onLeftDoubleClick != nil: this.onLeftDoubleClick(this, newEventArgs()) 249 | if this.mCmenuUsed and (this.mTrig and 2) == 2: 250 | this.mCmenu.showMenu(0) 251 | 252 | of WM_RBUTTONDOWN: 253 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 254 | if this.onRightMouseDown != nil: this.onRightMouseDown(this, newEventArgs()) 255 | 256 | of WM_RBUTTONUP: 257 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 258 | if this.onRightMouseUp != nil: this.onRightMouseUp(this, newEventArgs()) 259 | if this.onRightClick != nil: this.onRightClick(this, newEventArgs()) 260 | if this.mCmenuUsed and (this.mTrig and 4) == 4: 261 | this.mCmenu.showMenu(0) 262 | 263 | of WM_MOUSEMOVE: 264 | var this = cast[TrayIcon](GetWindowLongPtrW(hw, GWLP_USERDATA)) 265 | if this.onMouseMove != nil: this.onMouseMove(this, newEventArgs()) 266 | 267 | else: return DefWindowProcW(hw, msg, wpm, lpm) 268 | 269 | 270 | else: return DefWindowProcW(hw, msg, wpm, lpm) 271 | return DefWindowProcW(hw, msg, wpm, lpm) -------------------------------------------------------------------------------- /Nimforms/widestring.nim: -------------------------------------------------------------------------------- 1 | # Created on 13-May-2025 14:35 2 | 3 | 4 | converter wptr(this: WideString): LPWSTR {.inline.} = 5 | result = this.mData[0].addr 6 | 7 | converter cptr(this: WideString): LPCWSTR {.inline.} = 8 | result = this.mData[0].addr 9 | 10 | template `&`(this: WideString): ptr WCHAR = this.mData[0].addr 11 | 12 | proc strLen(this: WideString): int32 {.inline.} = this.mInputLen 13 | proc wcLen(this: WideString): int32 {.inline.} = this.mWcLen 14 | 15 | proc toStr(this: WideString) : string = 16 | let slen = WideCharToMultiByte(CP_UTF8, 0, &this, this.mWcLen, nil, 0, nil, nil ) 17 | result = newStringOfCap(slen) 18 | let x = WideCharToMultiByte(CP_UTF8, 0, &this, this.mWcLen, result[0].addr, slen, nil, nil ) 19 | 20 | 21 | # proc toWstrPtr(txt: string): LPWSTR = 22 | # let inpstr = txt.cstring 23 | # let slen = MultiByteToWideChar(CP_UTF8, 0, inpstr[0].addr, len(txt), nil, 0) 24 | # let bytes = (slen + 1) * 2 25 | # var wptr = cast[WArrayPtr](alloc0(bytes)) 26 | # let x = MultiByteToWideChar(CP_UTF8, 0, inpstr[0].addr, len(txt), wptr[0].addr, slen) 27 | # result = cast[LPWSTR](wptr) 28 | 29 | proc updateBuffer(this: var WideString, txt: string) = 30 | this.mInputStr = txt.cstring 31 | let slen = MultiByteToWideChar(CP_UTF8, 0, this.mInputStr[0].addr, int32(len(txt)), nil, 0) 32 | if slen > 0: 33 | if slen > this.mWcLen: 34 | dealloc(this.mData) 35 | this.mBytes = (slen + 1) * 2 36 | this.mData = cast[WArrayPtr](alloc0(this.mBytes)) 37 | 38 | let x = MultiByteToWideChar(CP_UTF8, 0, this.mInputStr[0].addr, int32(len(txt)), &this, slen) 39 | this.mWcLen = slen 40 | this.mData[slen] = cast[WCHAR](0) 41 | 42 | proc fillBuffer*(this: typedesc[WideString], buffer: ptr Utf16Char, txt: string) = 43 | let inpstr = txt.cstring 44 | let inplen = int32(len(txt)) 45 | let slen = MultiByteToWideChar(CP_UTF8, 0, inpstr[0].addr, inplen, nil, 0) 46 | if slen > 0: 47 | let x = MultiByteToWideChar(CP_UTF8, 0, inpstr[0].addr, inplen, buffer, slen) 48 | 49 | proc fillWstring(buffer: LPWSTR, txt: string) = 50 | let inpstr = txt.cstring 51 | let slen = MultiByteToWideChar(CP_UTF8, 0, inpstr[0].addr, int32(len(txt)), nil, 0) 52 | if slen > 0: 53 | let x = MultiByteToWideChar(CP_UTF8, 0, inpstr[0].addr, int32(len(txt)), buffer, slen) 54 | # buffer[slen] = cast[WCHAR](0) 55 | 56 | 57 | proc convertToUTF16(this: var WideString) = 58 | let slen = MultiByteToWideChar(CP_UTF8, 0, this.mInputStr[0].addr, this.mInputLen, nil, 0) 59 | this.mBytes = (slen + 1) * 2 60 | this.mData = cast[WArrayPtr](alloc0(this.mBytes)) 61 | let x = MultiByteToWideChar(CP_UTF8, 0, this.mInputStr[0].addr, this.mInputLen, &this, slen) 62 | this.mWcLen = slen 63 | this.mData[slen] = cast[WCHAR](0) 64 | # echo this.mInputStr, " - slen ", slen, ", data len ", this.mBytes, " bytes, x ", x 65 | 66 | proc newWideString*(txt: string): WideString = 67 | result.mInputStr = txt.cstring 68 | result.mInputLen = cast[int32](len(txt)) 69 | if result.mInputLen > 0: 70 | result.convertToUTF16() 71 | # echo "New Memory ", cast[int](result.mData) 72 | 73 | proc newWideString*(nChars: int32): WideString = 74 | result.mWcLen = nChars 75 | result.mBytes = (nChars + 1) * 2 76 | result.mData = cast[WArrayPtr](alloc0(result.mBytes)) 77 | 78 | 79 | proc newWideString*(src: WideString): WideString = 80 | result.mInputStr = src.mInputStr 81 | result.mInputLen = src.mInputLen 82 | result.mWcLen = src.mWcLen 83 | result.mBytes = src.mBytes 84 | if result.mInputLen > 0: 85 | result.mData = cast[WArrayPtr](alloc0(result.mBytes)) 86 | copyMem(&result, &src, result.mBytes) 87 | 88 | proc initWideString*(this: var WideString, src: WideString) = 89 | this.mInputStr = src.mInputStr 90 | this.mInputLen = src.mInputLen 91 | this.mBytes = src.mBytes 92 | if this.mInputLen > 0: 93 | this.mData = cast[WArrayPtr](alloc0(this.mBytes)) 94 | # echo "Allocated memory ", cast[int](this.mData) 95 | copyMem(&this, &src, this.mBytes) 96 | 97 | proc copyFrom(this: var WideString, src: WideString ) = 98 | this.mInputStr = src.mInputStr 99 | this.mInputLen = src.mInputLen 100 | copyMem(&this, &src, this.mBytes) 101 | 102 | proc ensureSize(this: var WideString, nChars: int32) = 103 | if this.mWcLen <= nChars: 104 | dealloc(this.mData) 105 | this.mBytes = (nChars + 1) * 2 106 | this.mData = cast[WArrayPtr](alloc0(this.mBytes)) 107 | this.mWcLen = nChars 108 | 109 | 110 | 111 | proc finalize*(this: var WideString) = 112 | # echo "WidesString dtor started, mem ", cast[int](this.mData) 113 | if this.mData != nil: 114 | dealloc(this.mData) 115 | echo "WidesString ", this.mInputStr, " deleted!" -------------------------------------------------------------------------------- /Nimforms/winmessages.nim: -------------------------------------------------------------------------------- 1 | # winmessages module - Created on 26-Mar-2023 12:22 PM 2 | # This module contains all the necessary windows messages. 3 | const 4 | WM_NULL = 0x0000 5 | WM_CREATE = 0x0001 6 | WM_DESTROY = 0x0002 7 | WM_MOVE = 0x0003 8 | WM_SIZE = 0x0005 9 | WM_ACTIVATE = 0x0006 10 | WA_INACTIVE = 0 11 | WA_ACTIVE = 1 12 | WA_CLICKACTIVE = 2 13 | WM_SETFOCUS = 0x0007 14 | WM_KILLFOCUS = 0x0008 15 | WM_ENABLE = 0x000A 16 | WM_SETREDRAW = 0x000B 17 | WM_SETTEXT = 0x000C 18 | WM_GETTEXT = 0x000D 19 | WM_GETTEXTLENGTH = 0x000E 20 | WM_PAINT = 0x000F 21 | WM_CLOSE = 0x0010 22 | WM_QUERYENDSESSION = 0x0011 23 | WM_QUERYOPEN = 0x0013 24 | WM_ENDSESSION = 0x0016 25 | WM_QUIT = 0x0012 26 | WM_ERASEBKGND = 0x0014 27 | WM_SYSCOLORCHANGE = 0x0015 28 | WM_SHOWWINDOW = 0x0018 29 | WM_WININICHANGE = 0x001A 30 | WM_SETTINGCHANGE = WM_WININICHANGE 31 | WM_DEVMODECHANGE = 0x001B 32 | WM_ACTIVATEAPP = 0x001C 33 | WM_FONTCHANGE = 0x001D 34 | WM_TIMECHANGE = 0x001E 35 | WM_CANCELMODE = 0x001F 36 | WM_SETCURSOR = 0x0020 37 | WM_MOUSEACTIVATE = 0x0021 38 | WM_CHILDACTIVATE = 0x0022 39 | WM_QUEUESYNC = 0x0023 40 | WM_GETMINMAXINFO = 0x0024 41 | WM_PAINTICON = 0x0026 42 | WM_ICONERASEBKGND = 0x0027 43 | WM_NEXTDLGCTL = 0x0028 44 | WM_SPOOLERSTATUS = 0x002A 45 | WM_DRAWITEM = 0x002B 46 | WM_MEASUREITEM = 0x002C 47 | WM_DELETEITEM = 0x002D 48 | WM_VKEYTOITEM = 0x002E 49 | WM_CHARTOITEM = 0x002F 50 | WM_SETFONT = 0x0030 51 | WM_GETFONT = 0x0031 52 | WM_SETHOTKEY = 0x0032 53 | WM_GETHOTKEY = 0x0033 54 | WM_QUERYDRAGICON = 0x0037 55 | WM_COMPAREITEM = 0x0039 56 | WM_GETOBJECT = 0x003D 57 | WM_COMPACTING = 0x0041 58 | WM_COMMNOTIFY = 0x0044 59 | WM_WINDOWPOSCHANGING = 0x0046 60 | WM_WINDOWPOSCHANGED = 0x0047 61 | WM_POWER = 0x0048 62 | PWR_OK = 1 63 | PWR_FAIL = -1 64 | PWR_SUSPENDREQUEST = 1 65 | PWR_SUSPENDRESUME = 2 66 | PWR_CRITICALRESUME = 3 67 | WM_COPYDATA = 0x004A 68 | WM_CANCELJOURNAL = 0x004B 69 | WM_NOTIFY = 0x004E 70 | WM_INPUTLANGCHANGEREQUEST = 0x0050 71 | WM_INPUTLANGCHANGE = 0x0051 72 | WM_TCARD = 0x0052 73 | WM_HELP = 0x0053 74 | WM_USERCHANGED = 0x0054 75 | WM_NOTIFYFORMAT = 0x0055 76 | NFR_ANSI = 1 77 | NFR_UNICODE = 2 78 | NF_QUERY = 3 79 | NF_REQUERY = 4 80 | WM_CONTEXTMENU = 0x007B 81 | WM_STYLECHANGING = 0x007C 82 | WM_STYLECHANGED = 0x007D 83 | WM_DISPLAYCHANGE = 0x007E 84 | WM_GETICON = 0x007F 85 | WM_SETICON = 0x0080 86 | WM_NCCREATE = 0x0081 87 | WM_NCDESTROY = 0x0082 88 | WM_NCCALCSIZE = 0x0083 89 | WM_NCHITTEST = 0x0084 90 | WM_NCPAINT = 0x0085 91 | WM_NCACTIVATE = 0x0086 92 | WM_GETDLGCODE = 0x0087 93 | WM_SYNCPAINT = 0x0088 94 | WM_NCMOUSEMOVE = 0x00A0 95 | WM_NCLBUTTONDOWN = 0x00A1 96 | WM_NCLBUTTONUP = 0x00A2 97 | WM_NCLBUTTONDBLCLK = 0x00A3 98 | WM_NCRBUTTONDOWN = 0x00A4 99 | WM_NCRBUTTONUP = 0x00A5 100 | WM_NCRBUTTONDBLCLK = 0x00A6 101 | WM_NCMBUTTONDOWN = 0x00A7 102 | WM_NCMBUTTONUP = 0x00A8 103 | WM_NCMBUTTONDBLCLK = 0x00A9 104 | WM_NCXBUTTONDOWN = 0x00AB 105 | WM_NCXBUTTONUP = 0x00AC 106 | WM_NCXBUTTONDBLCLK = 0x00AD 107 | WM_INPUT_DEVICE_CHANGE = 0x00fe 108 | WM_INPUT = 0x00FF 109 | WM_KEYFIRST = 0x0100 110 | WM_KEYDOWN = 0x0100 111 | WM_KEYUP = 0x0101 112 | WM_CHAR = 0x0102 113 | WM_DEADCHAR = 0x0103 114 | WM_SYSKEYDOWN = 0x0104 115 | WM_SYSKEYUP = 0x0105 116 | WM_SYSCHAR = 0x0106 117 | WM_SYSDEADCHAR = 0x0107 118 | WM_UNICHAR = 0x0109 119 | WM_KEYLAST = 0x0109 120 | UNICODE_NOCHAR = 0xFFFF 121 | WM_IME_STARTCOMPOSITION = 0x010D 122 | WM_IME_ENDCOMPOSITION = 0x010E 123 | WM_IME_COMPOSITION = 0x010F 124 | WM_IME_KEYLAST = 0x010F 125 | WM_INITDIALOG = 0x0110 126 | WM_COMMAND = 0x0111 127 | WM_SYSCOMMAND = 0x0112 128 | WM_TIMER = 0x0113 129 | WM_HSCROLL = 0x0114 130 | WM_VSCROLL = 0x0115 131 | WM_INITMENU = 0x0116 132 | WM_INITMENUPOPUP = 0x0117 133 | WM_MENUSELECT = 0x011F 134 | WM_GESTURE = 0x0119 135 | WM_GESTURENOTIFY = 0x011A 136 | WM_MENUCHAR = 0x0120 137 | WM_ENTERIDLE = 0x0121 138 | WM_MENURBUTTONUP = 0x0122 139 | WM_MENUDRAG = 0x0123 140 | WM_MENUGETOBJECT = 0x0124 141 | WM_UNINITMENUPOPUP = 0x0125 142 | WM_MENUCOMMAND = 0x0126 143 | WM_CHANGEUISTATE = 0x0127 144 | WM_UPDATEUISTATE = 0x0128 145 | WM_QUERYUISTATE = 0x0129 146 | UIS_SET = 1 147 | UIS_CLEAR = 2 148 | UIS_INITIALIZE = 3 149 | UISF_HIDEFOCUS = 0x1 150 | UISF_HIDEACCEL = 0x2 151 | UISF_ACTIVE = 0x4 152 | WM_CTLCOLORMSGBOX = 0x0132 153 | WM_CTLCOLOREDIT = 0x0133 154 | WM_CTLCOLORLISTBOX = 0x0134 155 | WM_CTLCOLORBTN = 0x0135 156 | WM_CTLCOLORDLG = 0x0136 157 | WM_CTLCOLORSCROLLBAR = 0x0137 158 | WM_CTLCOLORSTATIC = 0x0138 159 | MN_GETHMENU = 0x01E1 160 | WM_MOUSEFIRST = 0x0200 161 | WM_MOUSEMOVE = 0x0200 162 | WM_LBUTTONDOWN = 0x0201 163 | WM_LBUTTONUP = 0x0202 164 | WM_LBUTTONDBLCLK = 0x0203 165 | WM_RBUTTONDOWN = 0x0204 166 | WM_RBUTTONUP = 0x0205 167 | WM_RBUTTONDBLCLK = 0x0206 168 | WM_MBUTTONDOWN = 0x0207 169 | WM_MBUTTONUP = 0x0208 170 | WM_MBUTTONDBLCLK = 0x0209 171 | WM_MOUSEWHEEL = 0x020A 172 | WM_XBUTTONDOWN = 0x020B 173 | WM_XBUTTONUP = 0x020C 174 | WM_XBUTTONDBLCLK = 0x020D 175 | WM_MOUSEHWHEEL = 0x020e 176 | WM_MOUSELAST = 0x020e 177 | WHEEL_DELTA = 120 178 | XBUTTON1 = 0x0001 179 | XBUTTON2 = 0x0002 180 | WM_PARENTNOTIFY = 0x0210 181 | WM_ENTERMENULOOP = 0x0211 182 | WM_EXITMENULOOP = 0x0212 183 | WM_NEXTMENU = 0x0213 184 | WM_SIZING = 0x0214 185 | WM_CAPTURECHANGED = 0x0215 186 | WM_MOVING = 0x0216 187 | WM_POWERBROADCAST = 0x0218 188 | PBT_APMQUERYSUSPEND = 0x0000 189 | PBT_APMQUERYSTANDBY = 0x0001 190 | PBT_APMQUERYSUSPENDFAILED = 0x0002 191 | PBT_APMQUERYSTANDBYFAILED = 0x0003 192 | PBT_APMSUSPEND = 0x0004 193 | PBT_APMSTANDBY = 0x0005 194 | PBT_APMRESUMECRITICAL = 0x0006 195 | PBT_APMRESUMESUSPEND = 0x0007 196 | PBT_APMRESUMESTANDBY = 0x0008 197 | PBTF_APMRESUMEFROMFAILURE = 0x00000001 198 | PBT_APMBATTERYLOW = 0x0009 199 | PBT_APMPOWERSTATUSCHANGE = 0x000A 200 | PBT_APMOEMEVENT = 0x000B 201 | PBT_APMRESUMEAUTOMATIC = 0x0012 202 | PBT_POWERSETTINGCHANGE = 32787 203 | WM_DEVICECHANGE = 0x0219 204 | WM_MDICREATE = 0x0220 205 | WM_MDIDESTROY = 0x0221 206 | WM_MDIACTIVATE = 0x0222 207 | WM_MDIRESTORE = 0x0223 208 | WM_MDINEXT = 0x0224 209 | WM_MDIMAXIMIZE = 0x0225 210 | WM_MDITILE = 0x0226 211 | WM_MDICASCADE = 0x0227 212 | WM_MDIICONARRANGE = 0x0228 213 | WM_MDIGETACTIVE = 0x0229 214 | WM_MDISETMENU = 0x0230 215 | WM_ENTERSIZEMOVE = 0x0231 216 | WM_EXITSIZEMOVE = 0x0232 217 | WM_DROPFILES = 0x0233 218 | WM_MDIREFRESHMENU = 0x0234 219 | WM_POINTERDEVICECHANGE = 0x238 220 | WM_POINTERDEVICEINRANGE = 0x239 221 | WM_POINTERDEVICEOUTOFRANGE = 0x23a 222 | WM_TOUCH = 0x0240 223 | WM_NCPOINTERUPDATE = 0x0241 224 | WM_NCPOINTERDOWN = 0x0242 225 | WM_NCPOINTERUP = 0x0243 226 | WM_POINTERUPDATE = 0x0245 227 | WM_POINTERDOWN = 0x0246 228 | WM_POINTERUP = 0x0247 229 | WM_POINTERENTER = 0x0249 230 | WM_POINTERLEAVE = 0x024a 231 | WM_POINTERACTIVATE = 0x024b 232 | WM_POINTERCAPTURECHANGED = 0x024c 233 | WM_TOUCHHITTESTING = 0x024d 234 | WM_POINTERWHEEL = 0x024e 235 | WM_POINTERHWHEEL = 0x024f 236 | WM_IME_SETCONTEXT = 0x0281 237 | WM_IME_NOTIFY = 0x0282 238 | WM_IME_CONTROL = 0x0283 239 | WM_IME_COMPOSITIONFULL = 0x0284 240 | WM_IME_SELECT = 0x0285 241 | WM_IME_CHAR = 0x0286 242 | WM_IME_REQUEST = 0x0288 243 | WM_IME_KEYDOWN = 0x0290 244 | WM_IME_KEYUP = 0x0291 245 | WM_MOUSEHOVER = 0x02A1 246 | WM_MOUSELEAVE = 0x02A3 247 | WM_NCMOUSEHOVER = 0x02A0 248 | WM_NCMOUSELEAVE = 0x02A2 249 | WM_WTSSESSION_CHANGE = 0x02B1 250 | WM_TABLET_FIRST = 0x02c0 251 | WM_TABLET_LAST = 0x02df 252 | WM_CUT = 0x0300 253 | WM_COPY = 0x0301 254 | WM_PASTE = 0x0302 255 | WM_CLEAR = 0x0303 256 | WM_UNDO = 0x0304 257 | WM_RENDERFORMAT = 0x0305 258 | WM_RENDERALLFORMATS = 0x0306 259 | WM_DESTROYCLIPBOARD = 0x0307 260 | WM_DRAWCLIPBOARD = 0x0308 261 | WM_PAINTCLIPBOARD = 0x0309 262 | WM_VSCROLLCLIPBOARD = 0x030A 263 | WM_SIZECLIPBOARD = 0x030B 264 | WM_ASKCBFORMATNAME = 0x030C 265 | WM_CHANGECBCHAIN = 0x030D 266 | WM_HSCROLLCLIPBOARD = 0x030E 267 | WM_QUERYNEWPALETTE = 0x030F 268 | WM_PALETTEISCHANGING = 0x0310 269 | WM_PALETTECHANGED = 0x0311 270 | WM_HOTKEY = 0x0312 271 | WM_PRINT = 0x0317 272 | WM_PRINTCLIENT = 0x0318 273 | WM_APPCOMMAND = 0x0319 274 | WM_THEMECHANGED = 0x031A 275 | WM_CLIPBOARDUPDATE = 0x031d 276 | WM_DWMCOMPOSITIONCHANGED = 0x031e 277 | WM_DWMNCRENDERINGCHANGED = 0x031f 278 | WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320 279 | WM_DWMWINDOWMAXIMIZEDCHANGE = 0x0321 280 | WM_DWMSENDICONICTHUMBNAIL = 0x0323 281 | WM_DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326 282 | WM_GETTITLEBARINFOEX = 0x033f 283 | WM_HANDHELDFIRST = 0x0358 284 | WM_HANDHELDLAST = 0x035F 285 | WM_AFXFIRST = 0x0360 286 | WM_AFXLAST = 0x037F 287 | WM_PENWINFIRST = 0x0380 288 | WM_PENWINLAST = 0x038F 289 | WM_APP = 0x8000 290 | WM_USER = 0x0400 291 | 292 | # Window styles 293 | const 294 | WS_POPUP = 0x80000000 295 | WS_CHILD = 0x40000000 296 | WS_MINIMIZE = 0x20000000 297 | WS_VISIBLE = 0x10000000 298 | WS_DISABLED = 0x08000000 299 | WS_CLIPSIBLINGS = 0x04000000 300 | WS_CLIPCHILDREN = 0x02000000 301 | WS_MAXIMIZE = 0x01000000 302 | WS_CAPTION = 0x00C00000 303 | WS_BORDER = 0x00800000 304 | WS_DLGFRAME = 0x00400000 305 | WS_VSCROLL = 0x00200000 306 | WS_HSCROLL = 0x00100000 307 | WS_SYSMENU = 0x00080000 308 | WS_THICKFRAME = 0x00040000 309 | WS_GROUP = 0x00020000 310 | WS_TABSTOP = 0x00010000 311 | WS_MINIMIZEBOX = 0x00020000 312 | WS_MAXIMIZEBOX = 0x00010000 313 | WS_OVERLAPPED = 0x00000000 314 | WS_TILED = WS_OVERLAPPED 315 | WS_ICONIC = WS_MINIMIZE 316 | WS_SIZEBOX = WS_THICKFRAME 317 | WS_OVERLAPPEDWINDOW = WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_THICKFRAME or WS_MINIMIZEBOX or WS_MAXIMIZEBOX 318 | WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW 319 | WS_POPUPWINDOW = WS_POPUP or WS_BORDER or WS_SYSMENU 320 | WS_CHILDWINDOW = WS_CHILD 321 | WS_EX_DLGMODALFRAME = 0x00000001 322 | WS_EX_NOPARENTNOTIFY = 0x00000004 323 | WS_EX_TOPMOST = 0x00000008 324 | WS_EX_ACCEPTFILES = 0x00000010 325 | WS_EX_TRANSPARENT = 0x00000020 326 | WS_EX_MDICHILD = 0x00000040 327 | WS_EX_TOOLWINDOW = 0x00000080 328 | WS_EX_WINDOWEDGE = 0x00000100 329 | WS_EX_CLIENTEDGE = 0x00000200 330 | WS_EX_CONTEXTHELP = 0x00000400 331 | WS_EX_RIGHT = 0x00001000 332 | WS_EX_LEFT = 0x00000000 333 | WS_EX_RTLREADING = 0x00002000 334 | WS_EX_LTRREADING = 0x00000000 335 | WS_EX_LEFTSCROLLBAR = 0x00004000 336 | WS_EX_RIGHTSCROLLBAR = 0x00000000 337 | WS_EX_CONTROLPARENT = 0x00010000 338 | WS_EX_STATICEDGE = 0x00020000 339 | WS_EX_APPWINDOW = 0x00040000 340 | WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE or WS_EX_CLIENTEDGE 341 | WS_EX_PALETTEWINDOW = WS_EX_WINDOWEDGE or WS_EX_TOOLWINDOW or WS_EX_TOPMOST 342 | WS_EX_LAYERED = 0x00080000 343 | WS_EX_NOINHERITLAYOUT = 0x00100000 344 | WS_EX_NOREDIRECTIONBITMAP = 0x00200000 345 | WS_EX_LAYOUTRTL = 0x00400000 346 | WS_EX_COMPOSITED = 0x02000000 347 | WS_EX_NOACTIVATE = 0x08000000 348 | 349 | LVM_FIRST = 0x1000 350 | LVM_SETBKCOLOR = (LVM_FIRST + 1) 351 | 352 | # My messages 353 | const 354 | MM_NUMBER : UINT = WM_APP 355 | MM_MOUSE_LB_CLICK: UINT = MM_NUMBER + 1 356 | MM_MOUSE_RB_CLICK: UINT = MM_NUMBER + 2 357 | MM_NOTIFY_REFLECT: UINT = MM_NUMBER + 3 358 | MM_EDIT_COLOR: UINT = MM_NUMBER + 4 359 | MM_LABEL_COLOR: UINT = MM_NUMBER + 5 360 | MM_CTL_COMMAND: UINT = MM_NUMBER + 6 361 | MM_LIST_COLOR: UINT = MM_NUMBER + 7 362 | MM_BUDDY_RESIZE: UINT = MM_NUMBER + 8 363 | MM_HSCROLL: UINT = MM_NUMBER + 9 364 | MM_VSCROLL: UINT = MM_NUMBER + 10 365 | MM_MENU_EVENT: UINT = MM_NUMBER + 11 366 | MM_NODE_NOTIFY: UINT = MM_NUMBER + 12 367 | MM_MENU_ADDED: UINT = MM_NUMBER + 13 368 | MM_THREAD_MSG: UINT = WM_USER + 5 369 | MM_TRAY_MSG: UINT = MM_NUMBER + 14 370 | 371 | 372 | type # Key enum to represent all the keyboard keys 373 | Keys = enum 374 | keyModifier = -65536 375 | keyNone = 0, 376 | keyLButton, keyRButton, keyCancel, keyMButton, keyXButtonOne, keyXButtonTwo, 377 | keyBbackspace = 8, 378 | keyTab, keyLineFeed, 379 | keyClear = 12, 380 | keyEnter, 381 | keyShift = 16, 382 | keyCtrl, keyAlt, keyPause, keyCapsLock, 383 | keyEscape = 27, 384 | keySpace = 32, 385 | keyPageUp, keyPageDown, keyEnd, keyHome, keyLeftArrow, keyUpArrow, keyRightArrow, keyDownArrow, 386 | keySelect, keyPrint, keyExecute, keyPrintScreen, keyInsert, keyDelete, keyHelp, 387 | keyD0, keyD1, keyD2, keyD3, keyD4, keyD5, keyD6, keyD7, keyD8, keyD9, 388 | keyA = 65, 389 | keyB, keyC, keyD, keyE, keyF, keyG, keyH, keyI, keyJ, keyK, keyL, keyM, keyN, 390 | keyO, keyP, keyQ, keyR, keyS, keyT, keyU, keyV, keyW, keyX, keyY, keyZ, 391 | keyLeftWin, keyRightWin, keyApps, 392 | keySleep = 95, 393 | keyNumPad0, keyNumPad1, keyNumPad2, keyNumPad3, keyNumPad4, keyNumPad5, 394 | keyNumPad6, keyNumPad7, keyNumPad8, keyNumPad9, 395 | keyMultiply, keyAdd, keySeperator, keySubtract, keyDecimal, keyDivide, 396 | keyF1, keyF2, keyF3, keyF4, keyF5, keyF6, keyF7, keyF8, keyF9, keyF10, 397 | keyF11, keyF12, keyF13, keyF14, keyF15, keyF16, keyF17, keyF18, keyF19, keyF20, 398 | keyF21, keyF22, keyF23, keyF24, 399 | keyNumLock = 144, 400 | keyScroll, 401 | keyLeftShift = 160, 402 | keyRightShift, keyLeftCtrl, keyRightCtrl, keyLeftMenu, keyRightmenu, 403 | keyBrowserBack, keyBrowserForward, keyBrowerRefresh, keyBrowserStop, 404 | keyBrowserSearch, keyBrowserFavorites, keyBrowserHome, 405 | keyVolumeMute, keyVolumeDown, keyVolumeUp, 406 | keyMediaNextTrack, keyMediaPrevTrack, keyMediaStop, keyMediaPlayPause, launchMail, selectMedia, 407 | keyLaunchApp1, keyLaunchApp2, 408 | keyOEM1 = 186, 409 | keyOEMPlus, keyOEMComma, keyOEMMinus, keyOEMPeriod, keyOEMQuestion, keyOEMTilde, 410 | keyOEMOpenBracket = 219, 411 | keyOEMPipe, keyOEMCloseBracket, keyOEMQuotes, keyOEM8, 412 | keyOEMBackSlash = 226, 413 | keyProcess = 229, 414 | keyPacket = 231, 415 | keyAttn = 246, 416 | keyCrSel, keyExSel, keyEraseEof, keyPlay, keyZoom, keyNoName, keyPa1, keyOEMClear, # start from 400 417 | keyKeyCode = 65535, 418 | keyShiftModifier = 65536, 419 | keyCtrlModifier = 131072, 420 | keyAltModifier = 262144, -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nimforms 2 | A simple gui library for Nim programming language based on Windows API 3 | 4 | ## Screenshots 5 | All controls 6 | ![image](nimforms_130824.jpg) 7 | 8 | ## How to use: 9 | 1. Clone or download the repo. 10 | 2. Place the **Nimforms** folder in your project folder. 11 | 3. Import **Nimforms/nimforms** module in your source file. And start coding. 12 | 4. NOTE: Copy and paste the `app.exe.manifest` file from this repo to your exe location. And rename the file with your exe file's name. For example, if your exe file's name is `program.exe`, the manifest file must be `program.exe.manifest`. 13 | 5. IMPORTANT NOTE: Use the `--mm:refc` flag when compiling your nim file. 14 | 15 | ## Sample code 16 | This is the code that created the window in above screenshot 17 | ```nim 18 | 19 | import nimforms 20 | 21 | var frm = newForm("Nimforms GUI Library", 900, 500) 22 | frm.onMouseUp = proc(c: Control, e: MouseEventArgs) = echo "X: " & $e.x & " Y: " & $e.y 23 | frm.createHandle(true) 24 | 25 | # Let's create a tray icon 26 | var ti = newTrayIcon("Nimforms tray icon!", "nficon.ico") 27 | 28 | # Now add a context menu to our tray icon. 29 | ti.addContextMenu(TrayMenuTrigger.tmtAnyClick, "Windows", "|", "Linux", "ReactOS") 30 | 31 | # Add a click event handler for "Windows" menu. 32 | let winmenu = ti.contextMenu["Windows"] 33 | proc onWinmenuClick(c: MenuItem, e: EventArgs) {.handles: winmenu.onClick} = echo "Windows menu selected" 34 | 35 | var mbar = frm.addMenubar("Windows", "Linux", "ReactOS") 36 | mbar.menus["Windows"].addItems("Windows 8", "Windows 10", "|", "Windows 11") 37 | mbar.menus["Windows"].menus["Windows 11"].addItem("My OS") 38 | 39 | #Let's add a timer control which ticks in each 800 ms. 40 | var tmr = frm.addTimer(800, proc(c: Control, e: EventArgs) = echo "Timer ticked...") 41 | 42 | var btn = newButton(frm, "Normal") 43 | btn.onClick = proc(c: Control, e: EventArgs) = 44 | ti.showBalloon("Nimform", "Hi from Nimforms", 3000) # Button click will show the balloon 45 | 46 | var btn2 = newButton(frm, "Flat Color", btn->10) 47 | btn2.backColor = 0x83c5be 48 | 49 | btn2.onClick = proc(c: Control, e: EventArgs) = tmr.start() # Button click will start the timer 50 | 51 | var btn3 = newButton(frm, "Gradient", btn2.right(10)) 52 | btn3.setGradientColor(0xeeef20, 0x70e000) 53 | 54 | var dtp = newDateTimePicker(frm, btn3.right(10)) 55 | dtp.font = newFont("Tahoma", 14) 56 | dtp.foreColor = 0xe63946 57 | 58 | var cmb = newComboBox(frm, dtp.right(10), w=120) 59 | cmb.addItems("Windows", "MacOS", "Linux", "ReactOS") 60 | cmb.selectedIndex = 0 61 | 62 | var gb = newGroupBox(frm, "GroupBox1", 10, btn>>20, 120, 150) 63 | # gb.backColor = 0xa8dadc 64 | 65 | var lb = newLabel(frm, "Static Text", 20, gb.ypos + 30) 66 | lb.foreColor = 0x7b2cbf 67 | 68 | var lbx = newListBox(frm, gb.right(20), btn.bottom(20)) 69 | lbx.addItems("Windows", "Linux", "MacOS", "ReactOS") 70 | 71 | var lv = newListView(frm, lbx.right(10), btn.bottom(20), ("Windows", "Linux", "MacOS", 100, 100, 110)) 72 | lv.addRow("Win7", "openSUSE", "Mojave") 73 | lv.addRow("Win8", "Debian:", "Catalina") 74 | lv.addRow("Win10", "Fedora", " Big Sur") 75 | lv.addRow("Win11", "Ubuntu", "Monterey") 76 | 77 | var cmenu = lv.setContextMenu("Windows NT", "Linux", "|", "ReactOS") 78 | 79 | var np = newNumberPicker(frm, 20, lb.bottom(40)) 80 | np.decimalDigits = 2 81 | np.step = 0.5 82 | 83 | var np2 = newNumberPicker(frm, 20, np.bottom(10)) 84 | np2.buttonLeft = true 85 | np2.backColor = 0xffbf69 86 | 87 | var gb2 = newGroupBox(frm, "Compiler Options", 10, gb.bottom(20), 180, 170) 88 | var cb = newCheckBox(frm, "Threads On", gb2.xpos + 20, gb2.ypos + 40) 89 | var cb2 = newCheckBox(frm, "Hints off", gb2.xpos + 20, cb.bottom(10)) 90 | var rb = newRadioButton(frm, "Consoe App", gb2.xpos + 20, cb2.bottom(10)) 91 | var rb2 = newRadioButton(frm, "GUI App", gb2.xpos + 20, rb.bottom(10)) 92 | rb2.foreColor = 0xff0054 93 | 94 | var tb = newTextBox(frm, "Enter text", gb2.right(20), gb.bottom(40)) 95 | 96 | var tkb = newTrackBar(frm, gb2.right(20), tb.bottom(20), cdraw = true) 97 | 98 | var pgb = newProgressBar(frm, gb2.right(20), tkb.bottom(20), perc=true ) 99 | 100 | var tv = newTreeView(frm, pgb.right(20), lv.bottom(20), h=200) 101 | tv.addTreeNodeWithChilds("Windows", "Win7", "Win8", "Win10", "Win11") 102 | tv.addTreeNodeWithChilds("Linux", "openSUSE Leap 15.3", "Debian 11", "Fedora 35", "Ubuntu 22.04 LTS") 103 | tv.addTreeNodeWithChilds("MacOS", "Mojave (10.14)", "Catalina (10.15)", " Big Sur (11.0)", "Monterey (12.0)") 104 | 105 | var cal = newCalendar(frm, cmb.right(10), 10) 106 | 107 | proc onTrackChange(c: Control, e: EventArgs) {.handles:tkb.onValueChanged.} = 108 | pgb.value = tkb.value 109 | 110 | proc flatBtnClick(c: Control, e: EventArgs) = 111 | frm.setGradientBackColor(0xe85d04, 0xffba08) 112 | 113 | btn2.onClick = flatBtnClick 114 | 115 | let aw = cmenu["Windows NT"] 116 | 117 | proc addWork(m: MenuItem, e: EventArgs) {.handles: aw.onClick} = echo "Add Work menu clicked" 118 | 119 | # All set!! Now we can display our form on the screen. 120 | frm.display() 121 | 122 | ``` 123 | -------------------------------------------------------------------------------- /app.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app.nim: -------------------------------------------------------------------------------- 1 | 2 | import nimforms 3 | 4 | 5 | var frm = newForm("Nimforms GUI Library", 900, 500) 6 | frm.onMouseUp = proc(c: Control, e: MouseEventArgs) = echo "X: " & $e.x & " Y: " & $e.y 7 | frm.createHandle(true) 8 | 9 | 10 | # Let's create a tray icon 11 | var ti = newTrayIcon("Nimforms tray icon!", "nficon.ico") 12 | 13 | # Now add a context menu to our tray icon. 14 | # ti.addContextMenu(TrayMenuTrigger.tmtAnyClick, "Windows", "|", "Linux", "ReactOS") 15 | 16 | # Add a click event handler for "Windows" menu. 17 | # let winmenu = ti.contextMenu["Windows"] 18 | # proc onWinmenuClick(c: MenuItem, e: EventArgs) {.handles: winmenu.onClick} = echo "Windows menu selected" 19 | 20 | 21 | var mbar = frm.addMenubar("Windows", "Linux", "ReactOS") 22 | mbar.menus["Windows"].addItems("Windows 8", "Windows 10", "|", "Windows 11") 23 | mbar.menus["Windows"].menus["Windows 11"].addItem("My OS") 24 | 25 | #Let's add a timer control which ticks in each 800 ms. 26 | var tmr = frm.addTimer(800, proc(c: Control, e: EventArgs) = echo "Timer ticked...") 27 | 28 | 29 | var btn = newButton(frm, "Normal") 30 | btn.onClick = proc(c: Control, e: EventArgs) = 31 | ti.showBalloon("Nimform", "Hi from Nimforms", 3000) # Button click will show the balloon 32 | 33 | var btn2 = newButton(frm, "Flat Color", btn->10) 34 | btn2.backColor = 0x83c5be 35 | 36 | # btn2.onClick = proc(c: Control, e: EventArgs) = tmr.start() # Button click will start the timer 37 | 38 | 39 | var btn3 = newButton(frm, "Gradient", btn2.right(10)) 40 | btn3.setGradientColor(0xeeef20, 0x70e000) 41 | 42 | #---------------------- 43 | var dtp = newDateTimePicker(frm, btn3.right(10)) 44 | dtp.font = newFont("Tahoma", 14) 45 | dtp.foreColor = 0xe63946 46 | 47 | var cmb = newComboBox(frm, dtp.right(10), w=120) 48 | cmb.addItems("Windows", "MacOS", "Linux", "ReactOS") 49 | cmb.selectedIndex = 0 50 | 51 | var gb = newGroupBox(frm, "GroupBox1", 10, btn>>20, 120, 150) 52 | gb.backColor = 0xa8dadc 53 | 54 | var lb = newLabel(frm, "Static Text", 20, gb.ypos + 30) 55 | lb.printControlRect() 56 | # lb.foreColor = 0x7b2cbf 57 | gb.addControls(lb) 58 | 59 | var lbx = newListBox(frm, gb.right(20), btn.bottom(20)) 60 | lbx.addItems("Windows", "Linux", "MacOS", "ReactOS") 61 | 62 | var lv = newListView(frm, lbx.right(10), btn.bottom(20), ("Windows", "Linux", "MacOS", 100, 100, 110)) 63 | lv.addRow("Win7", "openSUSE", "Mojave") 64 | lv.addRow("Win8", "Debian:", "Catalina") 65 | lv.addRow("Win10", "Fedora", " Big Sur") 66 | lv.addRow("Win11", "Ubuntu", "Monterey") 67 | 68 | 69 | 70 | var np = newNumberPicker(frm, 20, lb.bottom(20)) 71 | np.decimalDigits = 2 72 | np.step = 0.5 73 | 74 | var np2 = newNumberPicker(frm, 20, np.bottom(10)) 75 | np2.buttonLeft = true 76 | np2.backColor = 0xffbf69 77 | 78 | var gb2 = newGroupBox(frm, "Compiler Options", 10, gb.bottom(20), 180, 170) #, GroupBoxStyle.gbsOverride) 79 | # gb2.style = GroupBoxStyle.gbsOverride 80 | gb2.setForeColor(0xd90429) 81 | 82 | gb2.changeFont("Trebuchet MS", 12) 83 | 84 | var cb = newCheckBox(frm, "Threads On", gb2.xpos + 20, gb2.ypos + 40) 85 | var cb2 = newCheckBox(frm, "Hints off", gb2.xpos + 20, cb.bottom(10)) 86 | var rb = newRadioButton(frm, "Consoe App", gb2.xpos + 20, cb2.bottom(10)) 87 | var rb2 = newRadioButton(frm, "GUI App", gb2.xpos + 20, rb.bottom(10)) 88 | rb2.foreColor = 0xff0054 89 | 90 | var tb = newTextBox(frm, "Enter text", gb2.right(20), gb.bottom(40)) 91 | 92 | var tkb = newTrackBar(frm, gb2.right(20), tb.bottom(20), cdraw = true) 93 | 94 | var pgb = newProgressBar(frm, gb2.right(20), tkb.bottom(20), perc=true ) 95 | 96 | var tv = newTreeView(frm, pgb.right(20), lv.bottom(20), h=200) 97 | tv.addTreeNodeWithChilds("Windows", "Win7", "Win8", "Win10", "Win11") 98 | tv.addTreeNodeWithChilds("Linux", "openSUSE Leap 15.3", "Debian 11", "Fedora 35", "Ubuntu 22.04 LTS") 99 | tv.addTreeNodeWithChilds("MacOS", "Mojave (10.14)", "Catalina (10.15)", " Big Sur (11.0)", "Monterey (12.0)") 100 | 101 | 102 | var cal = newCalendar(frm, cmb.right(25), 10) 103 | 104 | 105 | 106 | proc onTrackChange(c: Control, e: EventArgs) {.handles:tkb.onValueChanged.} = 107 | pgb.value = tkb.value 108 | 109 | 110 | proc flatBtnClick(c: Control, e: EventArgs) = 111 | # frm.backColor= 0xe63946 112 | frm.setGradientBackColor(0xe85d04, 0xffba08) 113 | btn2.onClick = flatBtnClick 114 | 115 | var cmenu = lv.setContextMenu("Add Work", "Give Work", "Finish Work") 116 | let aw = cmenu["Add Work"] 117 | 118 | proc addWork(m: MenuItem, e: EventArgs) {.handles: aw.onClick} = echo "Add Work menu clicked" 119 | 120 | # var lb5 = newLabel(frm, "Testing", 22, 100) 121 | 122 | 123 | 124 | # echo "Program starts " 125 | frm.display() 126 | 127 | -------------------------------------------------------------------------------- /nficon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kcvinker/Nimforms/d9a37419b83b2d08fd18293e4c7ece14d15baff5/nficon.ico -------------------------------------------------------------------------------- /nimforms_130824.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kcvinker/Nimforms/d9a37419b83b2d08fd18293e4c7ece14d15baff5/nimforms_130824.jpg --------------------------------------------------------------------------------