about summary refs log tree commit diff stats
path: root/shell/primitives.mu
Commit message (Collapse)AuthorAgeFilesLines
* rename grapheme to code-point-utf8Kartik K. Agaram2021-11-091-9/+9
| | | | | | Longer name, but it doesn't lie. We have no data structure right now for combining multiple code points. And it makes no sense for the notion of a grapheme to conflate its Unicode encoding.
* fix bad terminology: grapheme -> code pointKartik K. Agaram2021-08-291-1/+1
| | | | | | | | | | Unix text-mode terminals transparently support utf-8 these days, and so I treat utf-8 sequences (which I call graphemes in Mu) as fundamental. I then blindly carried over this state of affairs to bare-metal Mu, where it makes no sense. If you don't have a terminal handling font-rendering for you, fonts are most often indexed by code points and not utf-8 sequences.
* shell: render image from pbm data streamKartik K. Agaram2021-07-271-0/+209
|
* .Kartik K. Agaram2021-07-261-4/+9
| | | | | Smoked out some issues by rendering a single frame of Game of Life. Incredibly slow.
* .Kartik K. Agaram2021-07-261-3/+3
|
* shell primitive: initialize array of some sizeKartik K. Agaram2021-07-261-0/+78
|
* shell primitive: iset to mutate array at indexKartik K. Agaram2021-07-251-1/+104
|
* shell primitive: array indexKartik K. Agaram2021-07-251-1/+82
|
* shell: array typeKartik K. Agaram2021-07-251-0/+69
|
* .Kartik K. Agaram2021-07-251-4/+4
|
* shell: starting to implement arraysKartik K. Agaram2021-07-251-1/+65
|
* .Kartik K. Agaram2021-07-251-11/+10
|
* .Kartik K. Agaram2021-07-191-7/+2
|
* .Kartik K. Agaram2021-07-081-4/+4
|
* primitives for double-bufferingKartik K. Agaram2021-07-051-4/+152
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | I thought I needed these for this bouncing-ball demo: def (bounce screen) with (w (width screen) h (height screen) cx 16 cy 16 dx 12 dy 19) while 1 clear screen ring screen cx cy 16 3 5 cx += dx cy += dy when (or (cx > w) (cx < 0)) set dx 0-dx when (or (cy > h) (cy < 0)) set dy 0-dy for _ 0 (< _ 100) ++_ # delay No matter how I adjusted the delay I couldn't get rid of the jitter. So I built a double-buffered version: (bounce2 . [def (bounce2 screen) with (w (width screen) h (height screen) cx 16 cy 16 dx 12 dy 19 screen2 (new_screen (columns screen) (lines screen))) while 1 clear screen2 ring screen2 cx cy 16 3 5 cx += dx cy += dy when (or (cx > w) (cx < 0)) set dx 0-dx when (or (cy > h) (cy < 0)) set dy 0-dy blit screen2 screen for _ 0 (< _ 100) ++_]) # delay But it didn't make a difference! Turns out nothing will help you when successive frames are too far apart. This is the correct tweak to `bounce`: - dx 12 - dy 19) + dx 1 + dy (/ 19 12)) Still, we'll keep double-buffering around for the future.
* shell: fix clear on screensKartik K. Agaram2021-07-051-1/+1
| | | | | | | Broken since commit c95648c96 on Jul 3. Unclear what test to write for this. Should clear-stream check for NULL? Should apply-clear?
* expose Mu implementation of 'bezier'Kartik K. Agaram2021-07-051-1/+256
| | | | Still no support for acute-angled control points.
* replace 'circle' with Mu implementationKartik K. Agaram2021-07-051-1/+161
|
* replace 'vline' with Mu implementationKartik K. Agaram2021-07-051-1/+161
|
* replace 'hline' with Mu implementationKartik K. Agaram2021-07-051-1/+161
|
* replace 'line' with Mu implementationKartik K. Agaram2021-07-051-9/+196
|
* .Kartik K. Agaram2021-07-051-1/+1
|
* .Kartik K. Agaram2021-07-051-140/+140
|
* reading from streamsKartik K. Agaram2021-07-031-9/+115
| | | | | | The Mu shell has no string literals, only streams. No random access, only sequential access. But I've been playing fast and loose with its read pointer until now. Hopefully things are cleaned up now.
* new primitive: cons?Kartik K. Agaram2021-07-031-1/+42
|
* .Kartik K. Agaram2021-07-031-5/+10
|
* reorg primitives on screenKartik K. Agaram2021-07-021-30/+67
|
* snapshot: infixKartik K. Agaram2021-06-221-1/+1
| | | | | | | | | | | | | | | | | | | | | Like parenthesize, I'm copying tests over from https://github.com/akkartik/wart Unlike parenthesize, though, I can't just transliterate the code itself. Wart was operating on an intermediate AST representation. Here I'm all the way down to cells. That seemed like a good idea when I embarked, but now I'm not so sure. Operating with the right AST data structure allowed me to more easily iterate over the elements of a list. The natural recursion for cells is not a good fit. This patch and the next couple is an interesting case study in what makes Unix so effective. Yes, you have to play computer, and yes it gets verbose and ugly. But just diff and patch go surprisingly far in helping build a picture of the state space in my brain. Then again, there's a steep gradient of skills here. There are people who can visualize state spaces using diff and patch far better than me, and people who can't do it as well as me. Nature, nurture, having different priorities, whatever the reason. Giving some people just the right crutch excludes others.
* new macro: withKartik K. Agaram2021-06-201-1/+2
|
* .Kartik K. Agaram2021-06-121-1/+1
| | | | | Roll back to commit 70919b45f0. Recent commits add lots of extra function args for dubious benefit.
* eliminate some implicit writes to real screenKartik K. Agaram2021-06-121-1/+1
|
* try to abolish NULL from primitivesKartik K. Agaram2021-06-111-77/+504
|
* car/cdr of nil is now nilKartik K. Agaram2021-06-111-2/+18
|
* shell: remainder operationKartik K. Agaram2021-06-061-1/+65
|
* .Kartik K. Agaram2021-06-061-3/+3
|
* .Kartik K. Agaram2021-06-031-0/+1600
tate.screen_top1 = {line=1, pos=1} State.cursor1 = {line=1, pos=1} end end function edit.invalid1(State, loc1) if loc1.line > #State.lines then return true end local l = State.lines[loc1.line] if l.mode ~= 'text' then return false end -- pos is irrelevant to validity for a drawing line return loc1.pos > #State.lines[loc1.line].data end -- cursor loc in particular differs from other locs in one way: -- pos might occur just after end of line function edit.invalid_cursor1(State) local cursor1 = State.cursor1 if cursor1.line > #State.lines then return true end local l = State.lines[cursor1.line] if l.mode ~= 'text' then return false end -- pos is irrelevant to validity for a drawing line return cursor1.pos > #State.lines[cursor1.line].data + 1 end -- return y drawn until function edit.draw(State) App.color(Text_color) assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines)) assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)) State.cursor_x = nil State.cursor_y = nil local y = State.top local screen_bottom1 = {line=nil, pos=nil} --? print('== draw') for line_index = State.screen_top1.line,#State.lines do local line = State.lines[line_index] --? print('draw:', y, line_index, line) if y + State.line_height > App.screen.height then break end screen_bottom1.line = line_index --? print('text.draw', y, line_index) local startpos = 1 if line_index == State.screen_top1.line then startpos = State.screen_top1.pos end y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos) --? print('=> y', y) end State.screen_bottom1 = screen_bottom1 if State.search_term then Text.draw_search_bar(State) end return y end function edit.update(State, dt) end function edit.quit(State) end function edit.mouse_press(State, x,y, mouse_button) if State.search_term then return end State.mouse_down = mouse_button --? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos)) if y < State.top then State.old_cursor1 = State.cursor1 State.old_selection1 = State.selection1 State.mousepress_shift = App.shift_down() State.selection1 = { line=State.screen_top1.line, pos=State.screen_top1.pos, } return end for line_index,line in ipairs(State.lines) do if Text.in_line(State, line_index, x,y) then -- delicate dance between cursor, selection and old cursor/selection -- scenarios: -- regular press+release: sets cursor, clears selection -- shift press+release: -- sets selection to old cursor if not set otherwise leaves it untouched -- sets cursor -- press and hold to start a selection: sets selection on press, cursor on release -- press and hold, then press shift: ignore shift -- i.e. mouse_release should never look at shift state --? print_and_log(('edit.mouse_press: in line %d'):format(line_index)) State.old_cursor1 = State.cursor1 State.old_selection1 = State.selection1 State.mousepress_shift = App.shift_down() State.selection1 = { line=line_index, pos=Text.to_pos_on_line(State, line_index, x, y), } return end end -- still here? mouse press is below all screen lines State.old_cursor1 = State.cursor1 State.old_selection1 = State.selection1 State.mousepress_shift = App.shift_down() State.selection1 = { line=State.screen_bottom1.line, pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1), } end function edit.mouse_release(State, x,y, mouse_button) if State.search_term then return end --? print_and_log(('edit.mouse_release(%d,%d): cursor at %d,%d'):format(x,y, State.cursor1.line, State.cursor1.pos)) State.mouse_down = nil if y < State.top then State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} edit.clean_up_mouse_press(State) return end for line_index,line in ipairs(State.lines) do if Text.in_line(State, line_index, x,y) then --? print_and_log(('edit.mouse_release: in line %d'):format(line_index)) State.cursor1 = { line=line_index, pos=Text.to_pos_on_line(State, line_index, x, y), } --? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos)) edit.clean_up_mouse_press(State) return end end -- still here? mouse release is below all screen lines State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) edit.clean_up_mouse_press(State) --? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos)) end function edit.clean_up_mouse_press(State) if State.mousepress_shift then if State.old_selection1.line == nil then State.selection1 = State.old_cursor1 else State.selection1 = State.old_selection1 end end State.old_cursor1, State.old_selection1, State.mousepress_shift = nil if eq(State.cursor1, State.selection1) then State.selection1 = {} end end function edit.mouse_wheel_move(State, dx,dy) if dy > 0 then State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} for i=1,math.floor(dy) do Text.up(State) end elseif dy < 0 then State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} for i=1,math.floor(-dy) do Text.down(State) end end end function edit.text_input(State, t) --? print('text input', t) if State.search_term then State.search_term = State.search_term..t Text.search_next(State) end end function edit.keychord_press(State, chord, key) if State.selection1.line and -- printable character created using shift key => delete selection -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys) (not App.shift_down() or utf8.len(key) == 1) and chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) then Text.delete_selection(State, State.left, State.right) end if State.search_term then for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll if chord == 'escape' then State.search_term = nil State.cursor1 = State.search_backup.cursor State.screen_top1 = State.search_backup.screen_top State.search_backup = nil Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks elseif chord == 'return' then State.search_term = nil State.search_backup = nil elseif chord == 'backspace' then local len = utf8.len(State.search_term) local byte_offset = Text.offset(State.search_term, len) State.search_term = string.sub(State.search_term, 1, byte_offset-1) elseif chord == 'down' then State.cursor1.pos = State.cursor1.pos+1 Text.search_next(State) elseif chord == 'up' then Text.search_previous(State) end return elseif chord == 'C-f' then State.search_term = '' State.search_backup = { cursor={line=State.cursor1.line, pos=State.cursor1.pos}, screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos}, } -- zoom elseif chord == 'C-=' then edit.update_font_settings(State, State.font_height+2) Text.redraw_all(State) elseif chord == 'C--' then if State.font_height > 2 then edit.update_font_settings(State, State.font_height-2) Text.redraw_all(State) end elseif chord == 'C-0' then edit.update_font_settings(State, 20) Text.redraw_all(State) -- clipboard elseif chord == 'C-a' then State.selection1 = {line=1, pos=1} State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1} elseif chord == 'C-c' then local s = Text.selection(State) if s then App.set_clipboard(s) end -- dispatch to text else for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll Text.keychord_press(State, chord) end end function edit.key_release(State, key, scancode) end function edit.update_font_settings(State, font_height) State.font_height = font_height love.graphics.setFont(love.graphics.newFont(State.font_height)) State.line_height = math.floor(font_height*1.3) end --== some methods for tests -- Insulate tests from some key globals so I don't have to change the vast -- majority of tests when they're modified for the real app. Test_margin_left = 25 Test_margin_right = 0 function edit.initialize_test_state() -- if you change these values, tests will start failing return edit.initialize_state( 15, -- top margin Test_margin_left, App.screen.width - Test_margin_right, 14, -- font height assuming default LÖVE font 15) -- line height end -- all text_input events are also keypresses -- TODO: handle chords of multiple keys function edit.run_after_text_input(State, t) edit.keychord_press(State, t) edit.text_input(State, t) edit.key_release(State, t) App.screen.contents = {} edit.update(State, 0) edit.draw(State) end -- not all keys are text_input function edit.run_after_keychord(State, chord) edit.keychord_press(State, chord) edit.key_release(State, chord) App.screen.contents = {} edit.update(State, 0) edit.draw(State) end function edit.run_after_mouse_click(State, x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button) edit.mouse_press(State, x,y, mouse_button) App.fake_mouse_release(x,y, mouse_button) edit.mouse_release(State, x,y, mouse_button) App.screen.contents = {} edit.update(State, 0) edit.draw(State) end function edit.run_after_mouse_press(State, x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button) edit.mouse_press(State, x,y, mouse_button) App.screen.contents = {} edit.update(State, 0) edit.draw(State) end function edit.run_after_mouse_release(State, x,y, mouse_button) App.fake_mouse_release(x,y, mouse_button) edit.mouse_release(State, x,y, mouse_button) App.screen.contents = {} edit.update(State, 0) edit.draw(State) end