Author Topic: and another one for your toolbox...  (Read 21328 times)

0 Members and 1 Guest are viewing this topic.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #15 on: August 06, 2019, 09:11:46 am »
And another rather simple routine for your toolbox collection -- a quick mouse handler.

Code: QB64: [Select]
  1.     CLS
  2.     held$ = ""
  3.     SELECT CASE MBS
  4.         CASE 1: left = left + 1
  5.         CASE 2: right = right + 1
  6.         CASE 3: middle = middle + 1
  7.         CASE 4: held$ = "Left held"
  8.         CASE 5: held$ = "Right held"
  9.         CASE 6: held$ = "Middle held"
  10.     END SELECT
  11.  
  12.     PRINT "MouseX: "; _MOUSEX
  13.     PRINT "MouseY: "; _MOUSEY
  14.     PRINT "Left pressed: "; left
  15.     PRINT "Right pressed: "; right
  16.     PRINT "Middle pressed: "; middle
  17.     PRINT held$
  18.     _LIMIT 10
  19.  
  20.  
  21. FUNCTION MBS 'Mouse Button Status
  22.     STATIC StartTimer AS _FLOAT
  23.     STATIC ButtonDown AS INTEGER
  24.     STATIC ClickCount AS INTEGER
  25.     CONST ClickLimit## = 0.2 '2/10th of a second to down, up a key to count as a CLICK.  Down longer counts as a HOLD event.
  26.  
  27.     WHILE _MOUSEINPUT: WEND 'Remark out this line, if mouse main input/clear is going to be handled manually in main program.
  28.  
  29.     IF StartTimer = 0 THEN
  30.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  31.             ButtonDown = 1: StartTimer = TIMER(0.01)
  32.         ELSEIF _MOUSEBUTTON(2) THEN
  33.             ButtonDown = 2: StartTimer = TIMER(0.01)
  34.         ELSEIF _MOUSEBUTTON(3) THEN
  35.             ButtonDown = 3: StartTimer = TIMER(0.01)
  36.         END IF
  37.     ELSE
  38.         BD = ButtonDown MOD 3
  39.         IF BD = 0 THEN BD = 3
  40.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  41.             IF _MOUSEBUTTON(BD) = 0 THEN MouseHandler = ButtonDown: ButtonDown = 0: StartTimer = 0
  42.         ELSE
  43.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  44.                 MouseHandler = 0: ButtonDown = 0: StartTimer = 0
  45.             ELSE 'We've now started the hold event
  46.                 MouseHandler = ButtonDown + 3
  47.             END IF
  48.         END IF
  49.     END IF

One quick command to tell you if a button was clicked or held, for any of the 3 mouse buttons.

Hi Steve,

Shall we assume you intended to set the Function MBS with the ButtonDown value before exiting function?
Code: QB64: [Select]
  1. _TITLE "MBS mouse button status by Steve" ' 2019-08-06
  2.  
  3.     CLS
  4.     held$ = ""
  5.     SELECT CASE MBS
  6.         CASE 1: left = left + 1
  7.         CASE 2: right = right + 1
  8.         CASE 3: middle = middle + 1
  9.         CASE 4: held$ = "Left held"
  10.         CASE 5: held$ = "Right held"
  11.         CASE 6: held$ = "Middle held"
  12.     END SELECT
  13.  
  14.     PRINT "MouseX: "; _MOUSEX
  15.     PRINT "MouseY: "; _MOUSEY
  16.     PRINT "Left pressed: "; left
  17.     PRINT "Right pressed: "; right
  18.     PRINT "Middle pressed: "; middle
  19.     PRINT held$
  20.     _LIMIT 10
  21.  
  22.  
  23. FUNCTION MBS 'Mouse Button Status
  24.     STATIC StartTimer AS _FLOAT
  25.     STATIC ButtonDown AS INTEGER
  26.     STATIC ClickCount AS INTEGER
  27.     CONST ClickLimit## = 0.2 '2/10th of a second to down, up a key to count as a CLICK.  Down longer counts as a HOLD event.
  28.  
  29.     WHILE _MOUSEINPUT: WEND 'Remark out this line, if mouse main input/clear is going to be handled manually in main program.
  30.  
  31.     IF StartTimer = 0 THEN
  32.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  33.             ButtonDown = 1: StartTimer = TIMER(0.01)
  34.         ELSEIF _MOUSEBUTTON(2) THEN
  35.             ButtonDown = 2: StartTimer = TIMER(0.01)
  36.         ELSEIF _MOUSEBUTTON(3) THEN
  37.             ButtonDown = 3: StartTimer = TIMER(0.01)
  38.         END IF
  39.     ELSE
  40.         BD = ButtonDown MOD 3
  41.         IF BD = 0 THEN BD = 3
  42.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  43.             IF _MOUSEBUTTON(BD) = 0 THEN MouseHandler = ButtonDown: ButtonDown = 0: StartTimer = 0
  44.         ELSE
  45.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  46.                 MouseHandler = 0: ButtonDown = 0: StartTimer = 0
  47.             ELSE 'We've now started the hold event
  48.                 MouseHandler = ButtonDown + 3
  49.             END IF
  50.         END IF
  51.     END IF
  52.     MBS = ButtonDown '<<< fix set function with ButtonDown ???
  53.  

hmm... second time in couple days you post a fine function but don't set the value, makes me wonder how much you test code before posting... maybe it's the meds or the summer heat?

wait... just making bplus feel useful ;-))


Actually I found this mouse code and tester interesting that Steve had posted a while back:
Code: QB64: [Select]
  1. _TITLE "Mouse events using device" 'Steve McNeill 2019-02-21
  2. DIM SHARED MouseX AS INTEGER, MouseY AS INTEGER
  3. DIM SHARED MouseWheel AS INTEGER
  4. DIM SHARED LeftMouse AS INTEGER, RightMouse AS INTEGER, MiddleMouse AS INTEGER
  5. DIM SHARED ClickThreshold AS SINGLE
  6. ClickThreshold = 0.2 'down and up in threshold of a second to count as a click.  If down for more then threshold, we just have a hold event.
  7.  
  8.  
  9. SCREEN _NEWIMAGE(640, 480, 32)
  10.  
  11.  
  12.  
  13.     _LIMIT 60
  14.  
  15.     UpdateMouseInfo
  16.     LOCATE 1, 1
  17.     PRINT MouseX, MouseY
  18.     PRINT MouseWheel
  19.     PRINT LeftMouse, MiddleMouse, RightMouse
  20.     IF LeftMouse AND 2 THEN leftclick = leftclick + 1
  21.     IF MiddleMouse AND 2 THEN middleclick = middleclick + 1
  22.     IF RightMouse AND 2 THEN rightclick = rightclick + 1
  23.     PRINT leftclick, middleclick, rightclick
  24.  
  25. LOOP UNTIL INKEY$ = CHR$(27) 'escape key exit
  26.  
  27. SUB UpdateMouseInfo
  28.     DIM SW AS INTEGER, SH AS INTEGER
  29.     DIM LM AS INTEGER, MM AS INTEGER, RM AS INTEGER
  30.  
  31.     STATIC leftdown AS SINGLE, middledown AS SINGLE, rightdown AS SINGLE
  32.  
  33.     WHILE _DEVICEINPUT(2): MouseWheel = MouseWheel + _WHEEL(3): WEND 'clear and update the mouse buffer
  34.  
  35.     SW = _WIDTH \ 2: SH = _HEIGHT \ 2
  36.     MouseX = _AXIS(1) * SW + SW: MouseY = _AXIS(2) * SH + SH
  37.  
  38.  
  39.  
  40.     LM = _BUTTON(1): MM = _BUTTON(2): RM = _BUTTON(3)
  41.  
  42.     IF leftdown THEN 'if it was down
  43.         IF LM = 0 THEN 'and is now up
  44.             IF TIMER - leftdown < ClickThreshold THEN
  45.                 LeftMouse = 2 'clicked
  46.             ELSE 'if it's still down
  47.                 LeftMouse = 0 'the mouse was just released
  48.             END IF
  49.             leftdown = 0 'timer is cleared either way
  50.         ELSE
  51.             LeftMouse = 1 'the left mouse is down , timer should have already been set
  52.         END IF
  53.     ELSE
  54.         IF LM THEN
  55.             leftdown = TIMER 'set the timer to see if we have click or hold events
  56.             LeftMouse = 1 'the left mouse is down
  57.         ELSE
  58.             LeftMouse = 0
  59.         END IF
  60.     END IF
  61.  
  62.     IF middledown THEN 'if it was down
  63.         IF MM = 0 THEN 'and is now up
  64.             IF TIMER - middledown < ClickThreshold THEN
  65.                 MiddleMouse = 2 'clicked
  66.             ELSE 'if it's still down
  67.                 MiddleMouse = 0 'the mouse was just released
  68.             END IF
  69.             middledown = 0 'timer is cleared either way
  70.         ELSE
  71.             MiddleMouse = 1 'the middle mouse is down , timer should have already been set
  72.         END IF
  73.     ELSE
  74.         IF MM THEN
  75.             middledown = TIMER 'set the timer to see if we have click or hold events
  76.             MiddleMouse = 1 'the middle mouse is down
  77.         ELSE
  78.             MiddleMouse = 0
  79.         END IF
  80.     END IF
  81.  
  82.     IF rightdown THEN 'if it was down
  83.         IF RM = 0 THEN 'and is now up
  84.             IF TIMER - rightdown < ClickThreshold THEN
  85.                 RightMouse = 2 'clicked
  86.             ELSE 'if it's still down
  87.                 RightMouse = 0 'the mouse was just released
  88.             END IF
  89.             rightdown = 0 'timer is cleared either way
  90.         ELSE
  91.             RightMouse = 1 'the right mouse is down , timer should have already been set
  92.         END IF
  93.     ELSE
  94.         IF RM THEN
  95.             rightdown = TIMER 'set the timer to see if we have click or hold events
  96.             RightMouse = 1 'the right mouse is down
  97.         ELSE
  98.             RightMouse = 0
  99.         END IF
  100.     END IF
  101.  
  102.  
  103.  
  104.  

I thought this useful because it handles the mouse wheel (my mouse has a wheel not a middle button) and left and right buttons.




« Last Edit: August 06, 2019, 09:24:20 am by bplus »

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • Steve’s QB64 Archive Forum
Re: and another one for your toolbox...
« Reply #16 on: August 06, 2019, 10:15:20 am »
Hi Steve,

Shall we assume you intended to set the Function MBS with the ButtonDown value before exiting function?

hmm... second time in couple days you post a fine function but don't set the value, makes me wonder how much you test code before posting... maybe it's the meds or the summer heat?

wait... just making bplus feel useful ;-))

Actually I found this mouse code and tester interesting that Steve had posted a while back:

I blame it on the stupid medication.  :P

Tested, it works -- as long as I don't go in and halfway rename things to make them "simpler" when I finish them.  (MouseHandler was the original name for the routine, but I renamed it to a simple "MBS" and didn't change everything like I should have, like a big idiot...)

Here's the version once again -- even enhanced this time to handle the middle scroll button for you!  :P

Code: QB64: [Select]
  1. _TITLE "MBS mouse button status by Steve" ' 2019-08-06
  2.  
  3.     CLS
  4.     held$ = ""
  5.     SELECT CASE MBS
  6.         CASE 1: left = left + 1
  7.         CASE 2: right = right + 1
  8.         CASE 3: middle = middle + 1
  9.         CASE 4: held$ = "Left held"
  10.         CASE 5: held$ = "Right held"
  11.         CASE 6: held$ = "Middle held"
  12.         CASE ELSE: scroll = scroll + MBS \ 10
  13.     END SELECT
  14.  
  15.     PRINT "MouseX: "; _MOUSEX
  16.     PRINT "MouseY: "; _MOUSEY
  17.     PRINT "Left pressed: "; left
  18.     PRINT "Right pressed: "; right
  19.     PRINT "Middle pressed: "; middle
  20.     PRINT "Mouse Wheel Scrolled: "; scroll
  21.     PRINT held$
  22.     _LIMIT 10
  23.  
  24.  
  25. FUNCTION MBS 'Mouse Button Status
  26.     STATIC StartTimer AS _FLOAT
  27.     STATIC ButtonDown AS INTEGER
  28.     STATIC ClickCount AS INTEGER
  29.     CONST ClickLimit## = 0.2 '2/10th of a second to down, up a key to count as a CLICK.  Down longer counts as a HOLD event.
  30.  
  31.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  32.         scroll = scroll + 10 * _MOUSEWHEEL
  33.         IF scroll THEN MBS = scroll: EXIT FUNCTION
  34.     WEND
  35.  
  36.     IF StartTimer = 0 THEN
  37.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  38.             ButtonDown = 1: StartTimer = TIMER(0.01)
  39.         ELSEIF _MOUSEBUTTON(2) THEN
  40.             ButtonDown = 2: StartTimer = TIMER(0.01)
  41.         ELSEIF _MOUSEBUTTON(3) THEN
  42.             ButtonDown = 3: StartTimer = TIMER(0.01)
  43.         END IF
  44.     ELSE
  45.         BD = ButtonDown MOD 3
  46.         IF BD = 0 THEN BD = 3
  47.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  48.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = ButtonDown: ButtonDown = 0: StartTimer = 0
  49.         ELSE
  50.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  51.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  52.             ELSE 'We've now started the hold event
  53.                 MBS = ButtonDown + 3
  54.             END IF
  55.         END IF
  56.     END IF
  57.  

https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #17 on: August 06, 2019, 12:37:58 pm »
Since we are talking about mouse input, here is what I have come to use or modify for specific needs of app for getting mouse clicks, getClick(mouseX, mouseY, keypress) returns whatever occurs first a left mouse click or keypress AFTER all previous key press and mouse button down detection has been cleared (before the sub was called), it also clears mouse button and key press before return execution to caller code.

This was needed when running complex dialog code like mBox or inputBox, it is important then to get the mouse and key press from dialog to NOT interfere with other mouse and key press events in caller code and vice versa...

It has been an ever evolving process to get everything to work together, here is getClick tested with latest version of mBox and inputBox from which I discovered that a _KEYCLEAR line I had commented out in last mBOX update was still needed (fixed in code above)!

Code: QB64: [Select]
  1. _TITLE "getClick testing with mBox and inputBox" ' by bplus 2018-05-12"
  2. '2019-01-02 added _KEYCLEAR   to clear keys hit waiting for enter press at INPUT
  3. '2019-03-02 discovered this does not work with mBox, can it be tested and fixed?
  4. ' Nope, impossible.
  5. 'can I get mBox to use getClick? No because I want to allow mouse to move mBox by grabbing title bar
  6. 'OK added stuff to clear old mb and they are now working together
  7.  
  8. '2019-08-06 More tests with mBox and now inputBox$()
  9. ' OK test if getClick plays nice with last update of mBox (2019-07-31) and vice versa
  10. ' and also inputBox. Discovered 2019-08-06 mBox did need a _KEYCLEAR! inputBox already had it.
  11.  
  12. CONST xmax = 800
  13. CONST ymax = 600
  14. SCREEN _NEWIMAGE(xmax, ymax, 32)
  15. _SCREENMOVE 360, 60
  16. m$ = "To exit this mBox click X box, press enter, spacebar or escape, to exit this tester press escape (again?) after mBox is off screen."
  17. m$ = m$ + CHR$(10) + "Otherwise when this box is off screen, click screen or enter letters."
  18. m$ = m$ + CHR$(10) + "Once screen is clicked or enter is pressed you should see an inputBox$ come up."
  19.  
  20.     'CLS
  21.     toggle = (toggle + 1) MOD 2
  22.     IF toggle THEN 'test mBox
  23.         mBox "Test mBox with getClick", m$
  24.         PRINT "mBox (Message Box) is a sub that returns nothing, it only provides info. "
  25.         PRINT "If you pressed esacpe from mBox, press again if want to quit."
  26.     ELSE
  27.         'test inputBox
  28.         r$ = inputBox$("Enter a string for inputBox to return to main program.", "Testing getClick with inputBox function.", 76)
  29.         IF r$ = "" THEN PRINT "You entered nothing or escaped inputBox." ELSE PRINT "inputBox Returned: *" + r$ + "*"
  30.     END IF
  31.     PRINT "     To test getClick (now), click (and drag if want) screen or make a single keypress."
  32.     PRINT "What you did should be detected by getClick and reported back to you here on main screen."
  33.  
  34.     'x = -1: y = -1: k = 0      'need this?
  35.     getClick x, y, k
  36.     IF k = 27 OR (k > 31 AND k < 126) THEN
  37.         PRINT "You pressed "; CHR$(k)
  38.         IF k = 27 OR k = 113 OR k = 81 THEN PRINT "Goodbye": END
  39.     ELSE
  40.         PRINT "(" + STR$(x) + ", " + STR$(y) + ")"
  41.     END IF
  42.     PRINT "All additional mouse clicks or keypresses will be ignored until:"
  43.     INPUT "PRESS ENTER TO CONTINUE with another mBox or inputBox$() test... "; wate$
  44.     _LIMIT 30
  45.  
  46. 'getClick returns the mouse x, y position WHEN THE MOUSE WAS RELEASED! or keypress ASC 27 or 32 to 125
  47. '2019-08-06 Test now with new mBox and inputBox procedures
  48. 'found  mBox needed a _KEYCLEAR, how about inputBox?  OK had _KEYCLEAR already
  49. SUB getClick (mx, my, q)
  50.     mb = _MOUSEBUTTON(1)
  51.     WHILE mb
  52.         WHILE _MOUSEINPUT: WEND '<<<<<<<<<<<<<<<<<<<<  clear previous mb
  53.         mb = _MOUSEBUTTON(1)
  54.     WEND
  55.     _KEYCLEAR 'clear previous key presses
  56.     mx = -1: my = -1: q = 0
  57.     DO WHILE mx = -1 AND my = -1
  58.         q = _KEYHIT
  59.         IF q = 27 OR (q > 31 AND q < 126) THEN _KEYCLEAR: EXIT SUB
  60.         i = _MOUSEINPUT: mb = _MOUSEBUTTON(1)
  61.         'IF mb THEN
  62.         DO WHILE mb 'wait for release
  63.             q = _KEYHIT
  64.             IF q = 27 OR (q > 31 AND q < 126) THEN EXIT SUB
  65.             i = _MOUSEINPUT: mb = _MOUSEBUTTON(1): mx = _MOUSEX: my = _MOUSEY
  66.             _LIMIT 1000
  67.         LOOP
  68.         _LIMIT 1000
  69.     LOOP
  70.  
  71. 'title$ limit is 57 chars, all lines are 58 chars max, version 2019-08-06
  72. 'THIS SUB NOW NEEDS SUB scnState(restoreTF) for saving and restoring screen settings
  73. SUB mBox (title AS STRING, m AS STRING)
  74.  
  75.     bg = &HBB000033
  76.     fg = &HFF33AAFF
  77.  
  78.     'first screen dimensions and items to restore at exit
  79.     DIM sw AS INTEGER, sh AS INTEGER
  80.     DIM curScrn AS LONG, backScrn AS LONG, mbx AS LONG 'some handles
  81.     DIM ti AS INTEGER, limit AS INTEGER 'ti = text index for t$(), limit is number of chars per line
  82.     DIM i AS INTEGER, j AS INTEGER, ff AS _BIT, add AS _BYTE 'index, flag and
  83.     DIM bxH AS INTEGER, bxW AS INTEGER 'first as cells then as pixels
  84.     DIM mb AS INTEGER, mx AS INTEGER, my AS INTEGER, mi AS INTEGER, grabx AS INTEGER, graby AS INTEGER
  85.     DIM tlx AS INTEGER, tly AS INTEGER 'top left corner of message box
  86.     DIM lastx AS INTEGER, lasty AS INTEGER, t AS STRING, b AS STRING, c AS STRING, tail AS STRING
  87.     DIM d AS STRING, r AS SINGLE, kh AS LONG
  88.  
  89.     'screen and current settings to restore at end ofsub
  90.     scnState 0
  91.     sw = _WIDTH: sh = _HEIGHT
  92.  
  93.     _KEYCLEAR '<<<<<<<<<<<<<<<<<<<< do i still need this?   YES! 2019-08-06 update!
  94.  
  95.     'screen snapshot
  96.     curScrn = _DEST
  97.     backScrn = _NEWIMAGE(sw, sh, 32)
  98.     _PUTIMAGE , curScrn, backScrn
  99.  
  100.     'setup t() to store strings with ti as index, linit 58 chars per line max, b is for build
  101.     REDIM t(0) AS STRING: ti = 0: limit = 58: b = ""
  102.     FOR i = 1 TO LEN(m)
  103.         c = MID$(m, i, 1)
  104.         'are there any new line signals, CR, LF or both? take CRLF or LFCR as one break but dbl LF or CR means blank line
  105.         SELECT CASE c
  106.             CASE CHR$(13) 'load line
  107.                 IF MID$(m, i + 1, 1) = CHR$(10) THEN i = i + 1
  108.                 t(ti) = b: b = "": ti = ti + 1: REDIM _PRESERVE t(ti) AS STRING
  109.             CASE CHR$(10)
  110.                 IF MID$(m, i + 1, 1) = CHR$(13) THEN i = i + 1
  111.                 t(ti) = b: b = "": ti = ti + 1: REDIM _PRESERVE t(ti)
  112.             CASE ELSE
  113.                 IF c = CHR$(9) THEN c = SPACE$(4): add = 4 ELSE add = 1
  114.                 IF LEN(b) + add > limit THEN
  115.                     tail = "": ff = 0
  116.                     FOR j = LEN(b) TO 1 STEP -1 'backup until find a space, save the tail end for next line
  117.                         d = MID$(b, j, 1)
  118.                         IF d = " " THEN
  119.                             t(ti) = MID$(b, 1, j - 1): b = tail + c: ti = ti + 1: REDIM _PRESERVE t(ti)
  120.                             ff = 1 'found space flag
  121.                             EXIT FOR
  122.                         ELSE
  123.                             tail = d + tail 'the tail grows!
  124.                         END IF
  125.                     NEXT
  126.                     IF ff = 0 THEN 'no break? OK
  127.                         t(ti) = b: b = c: ti = ti + 1: REDIM _PRESERVE t(ti)
  128.                     END IF
  129.                 ELSE
  130.                     b = b + c 'just keep building the line
  131.                 END IF
  132.         END SELECT
  133.     NEXT
  134.     t(ti) = b
  135.     bxH = ti + 3: bxW = limit + 2
  136.  
  137.     'draw message box
  138.     mbx = _NEWIMAGE(60 * 8, (bxH + 1) * 16, 32)
  139.     _DEST mbx
  140.     COLOR _RGB32(128, 0, 0), _RGB32(225, 225, 255)
  141.     LOCATE 1, 1: PRINT LEFT$(SPACE$((bxW - LEN(title) - 3) / 2) + title + SPACE$(bxW), bxW)
  142.     COLOR _RGB32(225, 225, 255), _RGB32(200, 0, 0)
  143.     LOCATE 1, bxW - 2: PRINT " X "
  144.     COLOR fg, bg
  145.     LOCATE 2, 1: PRINT SPACE$(bxW);
  146.     FOR r = 0 TO ti
  147.         LOCATE 1 + r + 2, 1: PRINT LEFT$(" " + t(r) + SPACE$(bxW), bxW);
  148.     NEXT
  149.     LOCATE 1 + bxH, 1: PRINT SPACE$(limit + 2);
  150.  
  151.     'now for the action
  152.     _DEST curScrn
  153.  
  154.     'convert to pixels the top left corner of box at moment
  155.     bxW = bxW * 8: bxH = bxH * 16
  156.     tlx = (sw - bxW) / 2: tly = (sh - bxH) / 2
  157.     lastx = tlx: lasty = tly
  158.     'now allow user to move it around or just read it
  159.     WHILE 1
  160.         CLS
  161.         _PUTIMAGE , backScrn
  162.         _PUTIMAGE (tlx, tly), mbx, curScrn
  163.         _DISPLAY
  164.         WHILE _MOUSEINPUT: WEND
  165.         mx = _MOUSEX: my = _MOUSEY: mb = _MOUSEBUTTON(1)
  166.         IF mb THEN
  167.             IF mx >= tlx AND mx <= tlx + bxW AND my >= tly AND my <= tly + 16 THEN 'mouse down on title bar
  168.                 IF mx >= tlx + bxW - 24 THEN EXIT WHILE
  169.                 grabx = mx - tlx: graby = my - tly
  170.                 DO WHILE mb 'wait for release
  171.                     mi = _MOUSEINPUT: mb = _MOUSEBUTTON(1)
  172.                     mx = _MOUSEX: my = _MOUSEY
  173.                     IF mx - grabx >= 0 AND mx - grabx <= sw - bxW AND my - graby >= 0 AND my - graby <= sh - bxH THEN
  174.                         'attempt to speed up with less updates
  175.                         IF ((lastx - (mx - grabx)) ^ 2 + (lasty - (my - graby)) ^ 2) ^ .5 > 10 THEN
  176.                             tlx = mx - grabx: tly = my - graby
  177.                             CLS
  178.                             _PUTIMAGE , backScrn
  179.                             _PUTIMAGE (tlx, tly), mbx, curScrn
  180.                             lastx = tlx: lasty = tly
  181.                             _DISPLAY
  182.                         END IF
  183.                     END IF
  184.                     _LIMIT 400
  185.                 LOOP
  186.             END IF
  187.         END IF
  188.         kh = _KEYHIT
  189.         IF kh = 27 OR kh = 13 OR kh = 32 THEN EXIT WHILE
  190.         _LIMIT 400
  191.     WEND
  192.  
  193.     'put things back
  194.     COLOR _RGB32(255, 255, 255), _RGB32(0, 0, 0): CLS '
  195.     _PUTIMAGE , backScrn
  196.     _DISPLAY
  197.     _FREEIMAGE backScrn
  198.     _FREEIMAGE mbx
  199.     scnState 1 'Thanks Steve McNeill
  200.  
  201. ' for saving and restoring screen settins
  202. SUB scnState (restoreTF AS INTEGER) 'Thanks Steve McNeill
  203.     STATIC Font AS LONG, DefaultColor AS _UNSIGNED LONG, BackGroundColor AS _UNSIGNED LONG, Dest AS LONG, Source AS LONG
  204.     STATIC row AS INTEGER, col AS INTEGER, autodisplay AS INTEGER, mb AS INTEGER
  205.     IF restoreTF THEN
  206.         _FONT Font
  207.         COLOR DefaultColor, BackGroundColor
  208.         _DEST Dest
  209.         _SOURCE Source
  210.         LOCATE row, col
  211.         IF autodisplay THEN _AUTODISPLAY ELSE _DISPLAY
  212.         _KEYCLEAR
  213.         WHILE _MOUSEINPUT: WEND 'clear mouse clicks
  214.         mb = _MOUSEBUTTON(1)
  215.         IF mb THEN
  216.             DO
  217.                 WHILE _MOUSEINPUT: WEND
  218.                 mb = _MOUSEBUTTON(1)
  219.                 _LIMIT 100
  220.             LOOP UNTIL mb = 0
  221.         END IF
  222.     ELSE
  223.         Font = _FONT: DefaultColor = _DEFAULTCOLOR: BackGroundColor = _BACKGROUNDCOLOR
  224.         Dest = _DEST: Source = _SOURCE
  225.         row = CSRLIN: col = POS(0): autodisplay = _AUTODISPLAY
  226.     END IF
  227.  
  228. ' You can grab this box by title and drag it around screen for full viewing while answering prompt.
  229. ' Only one line allowed for prompt$
  230. ' boxWidth is 4 more than the allowed length of input, it needs to be longer than title$ and prompt$ also
  231. ' Utilities > Input Box > Input Box 1 tester v 2019-07-31
  232. FUNCTION inputBox$ (prompt$, title$, boxWidth AS _BYTE)
  233.     DIM ForeColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG, White AS _UNSIGNED LONG
  234.     DIM sw AS INTEGER, sh AS INTEGER, curScrn AS LONG, backScrn AS LONG, ibx AS LONG 'some handles
  235.  
  236.     'colors
  237.     ForeColor = &HFF000055 '<  change as desired  prompt text color, back color or type in area
  238.     BackColor = &HFF6080CC '<  change as desired  used fore color in type in area
  239.     White = &HFFFFFFFF
  240.  
  241.     'items to restore at exit
  242.     scnState 0
  243.  
  244.     'screen snapshot
  245.     sw = _WIDTH: sh = _HEIGHT: curScrn = _DEST
  246.     backScrn = _NEWIMAGE(sw, sh, 32)
  247.     _PUTIMAGE , curScrn, backScrn
  248.  
  249.     'moving box around on screen
  250.     DIM bxW AS INTEGER, bxH AS INTEGER
  251.     DIM mb AS INTEGER, mx AS INTEGER, my AS INTEGER, mi AS INTEGER, grabx AS INTEGER, graby AS INTEGER
  252.     DIM tlx AS INTEGER, tly AS INTEGER 'top left corner of message box
  253.     DIM lastx AS INTEGER, lasty AS INTEGER
  254.     DIM inp$, kh&
  255.  
  256.     'draw message box
  257.     bxW = boxWidth * 8: bxH = 7 * 16
  258.     ibx = _NEWIMAGE(bxW, bxH, 32)
  259.     _DEST ibx
  260.     COLOR &HFF880000, White
  261.     LOCATE 1, 1: PRINT LEFT$(SPACE$(INT((boxWidth - LEN(title$) - 3)) / 2) + title$ + SPACE$(boxWidth), boxWidth)
  262.     COLOR White, &HFFBB0000
  263.     LOCATE 1, boxWidth - 2: PRINT " X "
  264.     COLOR ForeColor, BackColor
  265.     LOCATE 2, 1: PRINT SPACE$(boxWidth);
  266.     LOCATE 3, 1: PRINT LEFT$(SPACE$((boxWidth - LEN(prompt$)) / 2) + prompt$ + SPACE$(boxWidth), boxWidth);
  267.     LOCATE 4, 1: PRINT SPACE$(boxWidth);
  268.     LOCATE 5, 1: PRINT SPACE$(boxWidth);
  269.     LOCATE 6, 1: PRINT SPACE$(boxWidth);
  270.     inp$ = ""
  271.     GOSUB finishBox
  272.  
  273.     'convert to pixels the top left corner of box at moment
  274.     bxW = boxWidth * 8: bxH = 5 * 16
  275.     tlx = (sw - bxW) / 2: tly = (sh - bxH) / 2
  276.     lastx = tlx: lasty = tly
  277.     _KEYCLEAR
  278.     'now allow user to move it around or just read it
  279.     WHILE 1
  280.         CLS
  281.         _PUTIMAGE , backScrn
  282.         _PUTIMAGE (tlx, tly), ibx, curScrn
  283.         _DISPLAY
  284.         WHILE _MOUSEINPUT: WEND
  285.         mx = _MOUSEX: my = _MOUSEY: mb = _MOUSEBUTTON(1)
  286.         IF mb THEN
  287.             IF mx >= tlx AND mx <= tlx + bxW AND my >= tly AND my <= tly + 16 THEN 'mouse down on title bar
  288.                 IF mx >= tlx + bxW - 24 THEN EXIT WHILE
  289.                 grabx = mx - tlx: graby = my - tly
  290.                 DO WHILE mb 'wait for release
  291.                     mi = _MOUSEINPUT: mb = _MOUSEBUTTON(1)
  292.                     mx = _MOUSEX: my = _MOUSEY
  293.                     IF mx - grabx >= 0 AND mx - grabx <= sw - bxW AND my - graby >= 0 AND my - graby <= sh - bxH THEN
  294.                         'attempt to speed up with less updates
  295.                         IF ((lastx - (mx - grabx)) ^ 2 + (lasty - (my - graby)) ^ 2) ^ .5 > 10 THEN
  296.                             tlx = mx - grabx: tly = my - graby
  297.                             CLS
  298.                             _PUTIMAGE , backScrn
  299.                             _PUTIMAGE (tlx, tly), ibx, curScrn
  300.                             lastx = tlx: lasty = tly
  301.                             _DISPLAY
  302.                         END IF
  303.                     END IF
  304.                     _LIMIT 400
  305.                 LOOP
  306.             END IF
  307.         END IF
  308.         kh& = _KEYHIT
  309.         SELECT CASE kh& 'whew not much for the main event!
  310.             CASE 13: EXIT WHILE
  311.             CASE 27: inp$ = "": EXIT WHILE
  312.             CASE 32 TO 128: IF LEN(inp$) < boxWidth - 4 THEN inp$ = inp$ + CHR$(kh&): GOSUB finishBox ELSE BEEP
  313.             CASE 8: IF LEN(inp$) THEN inp$ = LEFT$(inp$, LEN(inp$) - 1): GOSUB finishBox ELSE BEEP
  314.         END SELECT
  315.  
  316.         _LIMIT 60
  317.     WEND
  318.  
  319.     'put things back
  320.     scnState 1 'need fg and bg colors set to cls
  321.     CLS '? is this needed YES!!
  322.     _PUTIMAGE , backScrn
  323.     _DISPLAY
  324.     _FREEIMAGE backScrn
  325.     _FREEIMAGE ibx
  326.     scnState 1 'because we have to call _display, we have to call this again
  327.     inputBox$ = inp$
  328.  
  329.     finishBox:
  330.     _DEST ibx
  331.     COLOR BackColor, ForeColor
  332.     LOCATE 5, 2: PRINT LEFT$(" " + inp$ + SPACE$(boxWidth - 2), boxWidth - 2)
  333.     _DEST curScrn
  334.     RETURN
  335.  
  336.  


Offline johnno56

  • Forum Resident
  • Posts: 1270
  • Live long and prosper.
Re: and another one for your toolbox...
« Reply #18 on: August 06, 2019, 05:57:00 pm »
bplus,

Excellent menu. Very cool.

J
Logic is the beginning of wisdom.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #19 on: August 06, 2019, 07:18:23 pm »
Hi Johnno,

Menu? but thanks :D

I just got a new one today (palindrome):
Never odd or even.
« Last Edit: October 27, 2019, 03:16:18 pm by bplus »

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • Steve’s QB64 Archive Forum
Re: and another one for your toolbox...
« Reply #20 on: August 08, 2019, 09:51:23 pm »
How about an extended timer, to prevent those annoying bugs where you're running a program at midnight and the clock resets?

Code: QB64: [Select]
  1. SHELL "https://www.epochconverter.com/"
  2.     CLS
  3.     PRINT TIMER, INT(ExtendedTimer)
  4.     PRINT "Compare to the time at https://www.epochconverter.com/"
  5.     _DISPLAY
  6.     _LIMIT 10
  7.  
  8. FUNCTION ExtendedTimer##
  9.     d$ = DATE$
  10.     l = INSTR(d$, "-")
  11.     l1 = INSTR(l + 1, d$, "-")
  12.     m = VAL(LEFT$(d$, l))
  13.     d = VAL(MID$(d$, l + 1))
  14.     y = VAL(MID$(d$, l1 + 1)) - 1970
  15.     FOR i = 1 TO m
  16.         SELECT CASE i 'Add the number of days for each previous month passed
  17.             CASE 1: d = d 'January doestn't have any carry over days.
  18.             CASE 2, 4, 6, 8, 9, 11: d = d + 31
  19.             CASE 3: d = d + 28
  20.             CASE 5, 7, 10, 12: d = d + 30
  21.         END SELECT
  22.     NEXT
  23.     FOR i = 1 TO y
  24.         d = d + 365
  25.     NEXT
  26.     FOR i = 2 TO y STEP 4
  27.         IF m > 2 THEN d = d + 1 'add an extra day for leap year every 4 years, starting in 1970
  28.     NEXT
  29.     d = d - 1 'for year 2000
  30.     s~&& = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
  31.     ExtendedTimer## = (s~&& + TIMER)
  32.  
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #21 on: October 23, 2019, 08:02:28 pm »
Here is a math one, for calculating nCr without fully processed factorials to get around the 21 limit on factorials.
Code: QB64: [Select]
  1. OPTION _EXPLICIT ' B+ started 2019-10-23
  2. _TITLE "Alternate Calc of nCr, the number of Combinations of n things taken r at a time."
  3. ' Though this method is still quite limited at least it does not depend upon the full calculation
  4. ' of a factorial to return a result.
  5. ' You can do n up to at least 50 with r's at 1/2 n, and smaller r's for higher n's.
  6. ' Be very careful accepting returns as correct.
  7.  
  8. PRINT "The number of hands of 13 in a deck is ";
  9. PRINT USING "###,###,###,###,###.###"; nCr(52, 13)
  10. PRINT "                                compare to 635,013,559,600"
  11.     INPUT "Enter n, r to calculate nCr (0's to quit) "; n, r
  12.     IF n > 0 AND r > 0 AND r < n THEN
  13.         PRINT " nCr = "; nCr(n, r)
  14.     END IF
  15. LOOP UNTIL n = 0 OR r = 0
  16.  
  17. FUNCTION nCr~&& (n AS INTEGER, r AS INTEGER)
  18.     'Note: I am using notes from a successful calc to generalize this method.
  19.     ' How many hands of 13 from a deck?
  20.     ' Formula for Combination of N items taken R at a time = N!/(R! * (N-R)!) so we need
  21.     ' 52! / (39! * 13!) =    52 * 51 * 50 * 49 * ... * 40  / 13 * 12 * 11 * ...      * 1
  22.  
  23.     DIM a AS INTEGER, i AS INTEGER, j AS INTEGER, found AS INTEGER
  24.     DIM numerator AS _UNSIGNED _INTEGER64, denomenator AS _UNSIGNED LONG
  25.     a = n - r
  26.     IF a < r THEN r = a 'results are symmteric for r and n-r so use smaller of 2
  27.     a = n - r
  28.  
  29.     DIM numer(1 TO r) AS INTEGER, denom(1 TO r) AS INTEGER
  30.     ' DIM a52_39(1 TO 13) AS INTEGER, a13(1 TO 13) AS INTEGER
  31.     FOR i = 1 TO r
  32.         numer(i) = a + i ' = 40, 41, 42, 43, ... 52 numerator multipliers
  33.         denom(i) = i '     =  1,  2,  3,  4, ... 13 denominator multipliers
  34.     NEXT
  35.  
  36.     ' To keep the numerator from getting too big reduce by demoinator terms that divide evenly
  37.     FOR i = r TO 2 STEP -1
  38.         found = 0
  39.         FOR j = 1 TO r 'is the array item divisible by i
  40.             IF numer(j) MOD i = 0 THEN numer(j) = numer(j) \ i: found = 1: EXIT FOR
  41.         NEXT
  42.         IF found = 1 THEN denom(i) = 1
  43.     NEXT
  44.  
  45.     ' multiply whats left in numerator and denomiator
  46.     numerator = 1: denomenator = 1
  47.     FOR i = 1 TO r
  48.         numerator = numerator * numer(i) 'multiple numerators left
  49.         denomenator = denomenator * denom(i) 'multiply denominators left
  50.     NEXT
  51.     nCr~&& = numerator \ denomenator
  52.  

Here is a demo: https://www.qb64.org/forum/index.php?topic=1792.msg110362#msg110362

Update: 2019-10-28 You are safe using this function to get you past 20! with straight calculation nCR to n=52 after 52 you might want to check calculations against a ball park estimate. If nCr gets the amount of digits right then it is likely precise down to last digit. If the number of digits are off then obviously the precision doesn't matter, it wont be of use.

UPDATE: String Math$ has solved problem of extended factorials check this thread out starting about here:
https://www.qb64.org/forum/index.php?topic=2921.msg121797#msg121797
« Last Edit: September 12, 2020, 03:14:30 pm by bplus »

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
Re: and another one for your toolbox...
« Reply #22 on: October 26, 2019, 05:25:40 am »
I like this work-around bplus. Maybe a few functions along the lines of combinatorics wouldn't be a bad idea... And the problem of large numbers reminds me of something else...
You're not done when it works, you're done when it's right.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #23 on: October 27, 2019, 03:00:40 pm »
Thanks [banned user] for getting me to write this up. It is a must have for this collection:
Code: QB64: [Select]
  1. _TITLE "Quick demo of Fisher-Yates " 'B+ 2019-10-27
  2. ' 2019-10-28 slight change from Steve suggestion
  3.  
  4. low = -5
  5. high = 5
  6. DIM deck(low TO high) AS INTEGER
  7.  
  8. PRINT "Deck: ";
  9. FOR c = low TO high
  10.     deck(c) = c
  11.     PRINT c; ", ";
  12.  
  13. FOR i = 1 TO 5
  14.     PRINT "Shuffling..."
  15.     'F-Y shuffle, now 3 liner thanks to Steve suggestion
  16.     FOR c = high TO low + 1 STEP -1
  17.         SWAP deck(c), deck(INT(RND * (c - low + 1)) + low)
  18.     NEXT
  19.  
  20.     'show deck
  21.     PRINT "Deck: ";
  22.     FOR c = low TO high
  23.         PRINT deck(c); ", ";
  24.     NEXT
  25.     PRINT: PRINT
  26.  


Update: FY Shuffle now 3 liner thanks to Steve's suggestion, removed the variable assignment.
« Last Edit: October 28, 2019, 07:48:21 pm by bplus »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #24 on: October 27, 2019, 03:05:55 pm »
A very common problem, recently brought up by acjacques
https://www.qb64.org/forum/index.php?topic=1785.0

With less keystrokes than STR$(number), you can string it and trim it!
Code: QB64: [Select]
  1. FUNCTION ts$ (number)  'ts stands for trim string
  2.     ts$ = _TRIM$(STR$(number))
  3.  

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #25 on: October 27, 2019, 03:15:04 pm »
LoadSort but you have to adjust code to the Type of Array you are building and the type of Sort ie ascending of descending. Just modify the IF line as needed for the sort condition.

Just as the _TITLE says: Load and Sort an array at the same time, handy to automatically sort an array while you are loading it. Not intended for giant arrays that need number crunching routines to save time.

Code: QB64: [Select]
  1. _TITLE "LoadSort array" 'b+ 2020-06-07 from loadSortD
  2. 'this better than loadSortD because you don't need to share the array nor over DIM it
  3. ' It works like append filing the item into the array list
  4.     REDIM d(0) AS STRING 'clear last array, d(0) stays empty
  5.     FOR i = 1 TO 10
  6.         r$ = RIGHT$("  " + STR$(RND * 100 \ 1), 2)
  7.         IF r$ = "00" THEN r$ = " 0" '0 to 100 range
  8.         loadSort r$, d()
  9.         PRINT r$
  10.     NEXT
  11.     PRINT "Final array:"
  12.     FOR i = 1 TO 10 'note we never use d(0) left empty
  13.         PRINT i, d(i)
  14.     NEXT
  15.     INPUT "Press enter to do again, any other quits "; quit$
  16.     CLS
  17. LOOP UNTIL LEN(quit$)
  18.  
  19. SUB loadSort (insertN AS STRING, dynArr() AS STRING) ' note this leaves dynArr(0) empty! so ubound of array is also number of items in list
  20.     DIM ub, j, k
  21.     ub = UBOUND(dynArr) + 1
  22.     REDIM _PRESERVE dynArr(LBOUND(dynArr) TO ub) AS STRING
  23.     FOR j = 1 TO ub - 1
  24.         IF insertN > dynArr(j) THEN ' GT to LT according to descending or ascending sort
  25.             FOR k = ub TO j + 1 STEP -1
  26.                 dynArr(k) = dynArr(k - 1)
  27.             NEXT
  28.             EXIT FOR
  29.         END IF
  30.     NEXT
  31.     dynArr(j) = insertN
  32.  
  33.  

Embed code like this into place you are loading a smallish array you want sorted.

Edited with Steve's suggestion, thanks Steve!
x2 Edit thanks again Steve!
x3 Edit found another huge line savings, cut 4 lines, demo using Type Definition for array: https://www.qb64.org/forum/index.php?topic=1833.msg110748#msg110748

2020-06-07 Edit #4 now? While working on Gin Rummy, I needed to load card arrays according to highest points first. This new version is a sub routine, does not need oversized array already DIM and SHARED.

It is better than sAppend! if you need a sorted array while loading it.
« Last Edit: September 12, 2020, 03:04:18 pm by bplus »

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • Steve’s QB64 Archive Forum
Re: and another one for your toolbox...
« Reply #26 on: October 27, 2019, 03:35:19 pm »
You can remove that variable “stored” in the above and save some memory and processing.

Instead of IF stored = 0 THEN, change that statement to IF j = i THEN..

When you store a value, you exit J early, so it’s always less than I, but if it searches the whole loop, it terminates at (I - 1) + 1, which is I.

It saves setting and resetting the variable each pass of I.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #27 on: October 27, 2019, 04:05:22 pm »
Beautiful! Thanks Steve, code has been edited.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • Steve’s QB64 Archive Forum
Re: and another one for your toolbox...
« Reply #28 on: October 27, 2019, 05:01:00 pm »
Actually, you can simplify it even more.  Just remove the IF completely and make n(j) = num.

In the loop, you set n(j) then exit the loop.
Outside the loop, if I = J then you set n(I)...  but since I = J, you could set n(J) instead...

And, if you set n(j) inside the loop, as well as outside the loop, you can simplify down to just *always* setting it once at the end of the loop.  No IF needed, and no need to set it inside the loop either.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: and another one for your toolbox...
« Reply #29 on: October 27, 2019, 07:02:22 pm »
Ha! the less lines the more I like it, thanks again Steve!

Code edited again.
« Last Edit: October 27, 2019, 07:04:03 pm by bplus »