From 10b9162dc536d05965fb891ddfdadc1fa8386783 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 29 Dec 2024 23:19:34 +0100 Subject: [PATCH] improving fileselector --- .../michael_bull_kotlin_result_jvm.xml | 8 +- codeCore/build.gradle.kts | 4 +- codeGenCpu6502/build.gradle.kts | 4 +- codeGenExperimental/build.gradle.kts | 4 +- codeGenIntermediate/build.gradle.kts | 4 +- codeOptimizers/build.gradle.kts | 4 +- compiler/build.gradle.kts | 4 +- compiler/res/prog8lib/cx16/diskio.p8 | 3 + compiler/res/prog8lib/shared_cbm_diskio.p8 | 5 +- compiler/res/prog8lib/virtual/diskio.p8 | 3 + compilerAst/build.gradle.kts | 4 +- .../src/prog8/ast/antlr/Antlr2Kotlin.kt | 1 + docs/source/libraries.rst | 1 + examples/c64/fileselector.p8 | 221 +++++++++-------- examples/cx16/fileselector.p8 | 228 ++++++++++-------- examples/test.p8 | 58 +---- virtualmachine/build.gradle.kts | 4 +- 17 files changed, 288 insertions(+), 272 deletions(-) diff --git a/.idea/libraries/michael_bull_kotlin_result_jvm.xml b/.idea/libraries/michael_bull_kotlin_result_jvm.xml index cd63c0528..9614991a0 100644 --- a/.idea/libraries/michael_bull_kotlin_result_jvm.xml +++ b/.idea/libraries/michael_bull_kotlin_result_jvm.xml @@ -1,18 +1,18 @@ - + - + - + - + diff --git a/codeCore/build.gradle.kts b/codeCore/build.gradle.kts index 411a9a92a..afbd84098 100644 --- a/codeCore/build.gradle.kts +++ b/codeCore/build.gradle.kts @@ -7,7 +7,7 @@ plugins { dependencies { // should have no dependencies to other modules // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0") + implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1") } sourceSets { @@ -21,4 +21,4 @@ sourceSets { } } -// note: there are no unit tests in this module! \ No newline at end of file +// note: there are no unit tests in this module! diff --git a/codeGenCpu6502/build.gradle.kts b/codeGenCpu6502/build.gradle.kts index dd22990f4..02b82c51f 100644 --- a/codeGenCpu6502/build.gradle.kts +++ b/codeGenCpu6502/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { implementation(project(":codeCore")) // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") // implementation "org.jetbrains.kotlin:kotlin-reflect" - implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0") + implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1") testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1") testImplementation("io.kotest:kotest-framework-datatest:5.9.1") @@ -43,4 +43,4 @@ tasks.test { testLogging { events("skipped", "failed") } -} \ No newline at end of file +} diff --git a/codeGenExperimental/build.gradle.kts b/codeGenExperimental/build.gradle.kts index 8bf1d3295..930b48f42 100644 --- a/codeGenExperimental/build.gradle.kts +++ b/codeGenExperimental/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { implementation(project(":codeGenIntermediate")) // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") // implementation "org.jetbrains.kotlin:kotlin-reflect" - implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0") + implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1") } sourceSets { @@ -24,4 +24,4 @@ sourceSets { } } -// note: there are no unit tests in this module! \ No newline at end of file +// note: there are no unit tests in this module! diff --git a/codeGenIntermediate/build.gradle.kts b/codeGenIntermediate/build.gradle.kts index dfcb064be..a61e1ec32 100644 --- a/codeGenIntermediate/build.gradle.kts +++ b/codeGenIntermediate/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { implementation(project(":intermediate")) // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") // implementation "org.jetbrains.kotlin:kotlin-reflect" - implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0") + implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1") testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1") testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") @@ -45,4 +45,4 @@ tasks.test { testLogging { events("skipped", "failed") } -} \ No newline at end of file +} diff --git a/codeOptimizers/build.gradle.kts b/codeOptimizers/build.gradle.kts index d411e3820..34f38cb1d 100644 --- a/codeOptimizers/build.gradle.kts +++ b/codeOptimizers/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { implementation(project(":codeCore")) implementation(project(":compilerAst")) // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0") + implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1") // implementation "org.jetbrains.kotlin:kotlin-reflect" } @@ -23,4 +23,4 @@ sourceSets { } } -// note: there are no unit tests in this module! \ No newline at end of file +// note: there are no unit tests in this module! diff --git a/compiler/build.gradle.kts b/compiler/build.gradle.kts index 3a3d3c26b..b43764cb2 100644 --- a/compiler/build.gradle.kts +++ b/compiler/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") // implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.6") - implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0") + implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1") testImplementation(project(":codeCore")) testImplementation(project(":intermediate")) @@ -94,4 +94,4 @@ tasks.compileKotlin { tasks.compileJava { dependsOn(tasks.createVersionFile) -} \ No newline at end of file +} diff --git a/compiler/res/prog8lib/cx16/diskio.p8 b/compiler/res/prog8lib/cx16/diskio.p8 index 9e0e410ac..450cf6e69 100644 --- a/compiler/res/prog8lib/cx16/diskio.p8 +++ b/compiler/res/prog8lib/cx16/diskio.p8 @@ -162,6 +162,9 @@ io_error: ; After the last filename one additional 0 byte is placed to indicate the end of the list. ; Returns number of files (it skips 'dir' entries i.e. subdirectories). ; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer. + ; Note that no list of pointers of some form is returned, the names are just squashed together. + ; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names + ; and registering when the next one starts after the 0-byte separator. uword buffer_start = filenames_buffer ubyte files_found = 0 filenames_buffer[0]=0 diff --git a/compiler/res/prog8lib/shared_cbm_diskio.p8 b/compiler/res/prog8lib/shared_cbm_diskio.p8 index d147da6ba..f00fb216f 100644 --- a/compiler/res/prog8lib/shared_cbm_diskio.p8 +++ b/compiler/res/prog8lib/shared_cbm_diskio.p8 @@ -140,7 +140,10 @@ io_error: ; After the last filename one additional 0 byte is placed to indicate the end of the list. ; Returns number of files (it skips 'dir' entries i.e. subdirectories). ; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer. - uword buffer_start = filenames_buffer + ; Note that no list of pointers of some form is returned, the names are just squashed together. + ; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names + ; and registering when the next one starts after the 0-byte separator. + uword buffer_start = filenames_buffer ubyte files_found = 0 if lf_start_list(pattern_ptr) { while lf_next_entry() { diff --git a/compiler/res/prog8lib/virtual/diskio.p8 b/compiler/res/prog8lib/virtual/diskio.p8 index 6abb911ac..b23d981a4 100644 --- a/compiler/res/prog8lib/virtual/diskio.p8 +++ b/compiler/res/prog8lib/virtual/diskio.p8 @@ -24,6 +24,9 @@ diskio { ; After the last filename one additional 0 byte is placed to indicate the end of the list. ; Returns number of files (it skips 'dir' entries i.e. subdirectories). ; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer. + ; Note that no list of pointers of some form is returned, the names are just squashed together. + ; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names + ; and registering when the next one starts after the 0-byte separator. txt.print("@TODO: list_filenames\n") sys.clear_carry() return 0 diff --git a/compilerAst/build.gradle.kts b/compilerAst/build.gradle.kts index f1fee396d..493788d33 100644 --- a/compilerAst/build.gradle.kts +++ b/compilerAst/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { implementation(project(":codeCore")) // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.antlr:antlr4-runtime:4.13.2") - implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0") + implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1") implementation(project(":parser")) } @@ -22,4 +22,4 @@ sourceSets { srcDir("${project.projectDir}/src") } } -} \ No newline at end of file +} diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 3ba584760..eec024385 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -589,6 +589,7 @@ private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expressi val addressOf = addressof() val identifier = addressOf.scoped_identifier() val msb = addressOf.ADDRESS_OF_MSB()!=null + // note: &< (ADDRESS_OF_LSB) is equivalent to a regular &. return if (identifier != null) AddressOf(addressof().scoped_identifier().toAst(), null, msb, toPosition()) else { diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 02f3aa79a..f8dcca803 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -965,6 +965,7 @@ Provides string manipulation routines. ``pattern_match (string, pattern) -> bool`` (not on Virtual target) Returns true if the string matches the pattern, false if not. '?' in the pattern matches any one character. '*' in the pattern matches any substring. + An empty pattern matches nothing. If you need everything to match, use a single '*'. ``hash (string) -> ubyte`` Returns a simple 8 bit hash value for the given string. diff --git a/examples/c64/fileselector.p8 b/examples/c64/fileselector.p8 index bd6b2a4c0..45c0d15e9 100644 --- a/examples/c64/fileselector.p8 +++ b/examples/c64/fileselector.p8 @@ -1,16 +1,19 @@ %import diskio %import textio +%import strings %zeropage basicsafe -; A "GUI" for an interactive file selector, that scrolls the selection list if it doesn't fit on the screen. +; A "TUI" for an interactive file selector, that scrolls the selection list if it doesn't fit on the screen. +; Depends a lot on diskio routines, and uses the drive set in the diskio.drivenumber variable (usually just 8) -; TODO sort entries alphabetically? +; TODO sort entries alphabetically? Or not, because C64/C128 directories tend to be not very large. main { sub start() { - uword chosen = fileselector.select(8) + ;; fileselector.configure(1, 5, 5) + uword chosen = fileselector.select("*") txt.nl() if chosen!=0 { txt.print("\nchosen: ") @@ -25,96 +28,102 @@ main { fileselector { - uword filenamesbuf - const uword filenamesbuf_size = $1000 - const ubyte DIALOG_TOPX = 4 - const ubyte DIALOG_TOPY = 1 - const ubyte MAX_LINES = 15 + uword filenamesbuffer + uword filename_ptrs_start ; array of 127 string pointers for each of the names in the buffer. ends with $0000. + const uword filenamesbuf_size = $0f00 + ubyte dialog_topx = 4 + ubyte dialog_topy = 1 + ubyte max_lines = 15 str chosen_filename = "?" * 32 uword name_ptr ubyte num_visible_files - sub select(ubyte drive) -> uword { + sub configure(ubyte column, ubyte row, ubyte max_entries) { + dialog_topx = column + dialog_topy = row + max_lines = max_entries + } + + sub select(str pattern) -> uword { if sys.target==64 { ; c64 has memory at $c000 - filenamesbuf = $c000 + filenamesbuffer = $c000 + filename_ptrs_start = $cf00 } else if sys.target==128 { ; c128 has memory up to $c000 (if you use prog8's init code) - filenamesbuf = $b000 + filenamesbuffer = $b000 + filename_ptrs_start = $bf00 } else { ; assume PET32, memory upto $8000 - filenamesbuf = $7000 + filenamesbuffer = $7000 + filename_ptrs_start = $7f00 } num_visible_files = 0 chosen_filename[0] = 0 - diskio.drivenumber = drive name_ptr = diskio.diskname() if name_ptr==0 or cbm.READST()!=0 return 0 txt.cls() - txt.plot(DIALOG_TOPX, DIALOG_TOPY) + txt.plot(dialog_topx, dialog_topy) txt.print("┌") linepart() txt.print("┐\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("│ drive ") txt.print_ub(diskio.drivenumber) txt.print(": '") txt.print(name_ptr) txt.chrout('\'') - txt.column(DIALOG_TOPX+31) + txt.column(dialog_topx+31) txt.print("│\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("│ scanning directory... │\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) footerline() - ubyte num_files = diskio.list_filenames(0, filenamesbuf, filenamesbuf_size) ; use Hiram bank to store the files + ubyte num_files = diskio.list_filenames(pattern, filenamesbuffer, filenamesbuf_size) ; use Hiram bank to store the files ubyte selected_line ubyte top_index - uword[MAX_LINES] visible_names + uword filename_ptrs - txt.plot(DIALOG_TOPX+2, DIALOG_TOPY+2) + construct_name_ptr_array() + ; initial display + txt.plot(dialog_topx+2, dialog_topy+2) txt.print("select file: (") txt.print_ub(num_files) txt.print(" total)") - txt.column(DIALOG_TOPX+31) + txt.column(dialog_topx+31) txt.print("│\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("│ stop or q to abort |\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("├") linepart() txt.print("┤\n") print_up_indicator(false) - name_ptr = filenamesbuf - for selected_line in 0 to min(MAX_LINES, num_files)-1 { - if cbm.STOP2() break - - txt.column(DIALOG_TOPX) - txt.print("│ ") - - visible_names[selected_line] = name_ptr ; keep pointer to name - while @(name_ptr)!=0 { - txt.chrout(@(name_ptr)) - name_ptr++ + if num_files>0 { + for selected_line in 0 to min(max_lines, num_files)-1 { + txt.column(dialog_topx) + txt.print("│ ") + print_filename(peekw(filename_ptrs_start+selected_line*$0002)) + txt.column(dialog_topx+31) + txt.print("│\n") + num_visible_files++ } - txt.column(DIALOG_TOPX+31) + } else { + txt.column(dialog_topx) + txt.print("│ no matches.") + txt.column(dialog_topx+31) txt.print("│\n") - name_ptr++ - num_visible_files++ } print_down_indicator(false) - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) footerline() - - name_ptr = filenamesbuf selected_line = 0 invert(selected_line) - print_up_and_down() repeat { @@ -125,41 +134,48 @@ fileselector { when key { 3, 27, 'q' -> return 0 ; STOP or Q aborts (and ESC?) '\n',' ' -> { - void strings.copy(visible_names[selected_line], chosen_filename) - return chosen_filename + if num_files>0 { + void strings.copy(peekw(filename_ptrs_start + (top_index+selected_line)*$0002), chosen_filename) + return chosen_filename + } + return 0 } - '[' -> { + '[', 157 -> { ; cursor left ; previous page of lines invert(selected_line) if selected_line==0 - repeat MAX_LINES scroll_list_backward() + repeat max_lines scroll_list_backward() selected_line = 0 invert(selected_line) print_up_and_down() } - ']' -> { - ; next page of lines - invert(selected_line) - if selected_line == MAX_LINES-1 - repeat MAX_LINES scroll_list_forward() - selected_line = num_visible_files-1 - invert(selected_line) - print_up_and_down() + ']', 29 -> { ; cursor right + if num_files>0 { + ; next page of lines + invert(selected_line) + if selected_line == max_lines-1 + repeat max_lines scroll_list_forward() + selected_line = num_visible_files-1 + invert(selected_line) + print_up_and_down() + } } 17 -> { ; down - invert(selected_line) - if selected_lineMAX_LINES - scroll_list_forward() - invert(selected_line) - print_up_and_down() + if num_files>0 { + invert(selected_line) + if selected_linemax_lines + scroll_list_forward() + invert(selected_line) + print_up_and_down() + } } 145 -> { ; up invert(selected_line) if selected_line>0 selected_line-- - else if num_files>MAX_LINES + else if num_files>max_lines scroll_list_backward() invert(selected_line) print_up_and_down() @@ -169,25 +185,40 @@ fileselector { ubyte x,y - sub scroll_list_forward() { - if top_index+MAX_LINES< num_files { - top_index++ - ; scroll the displayed list up 1 - scroll_txt_up(DIALOG_TOPX+2, DIALOG_TOPY+6, 28, MAX_LINES, sc:' ') - ; shift the name pointer array 1 to the left - for y in 0 to MAX_LINES-2 - visible_names[y]=visible_names[y+1] - - ; set name pointer array last element with newly appeared name - name_ptr = visible_names[MAX_LINES-1] + sub construct_name_ptr_array() { + filename_ptrs = filename_ptrs_start + name_ptr = filenamesbuffer + repeat num_files { + pokew(filename_ptrs, name_ptr) while @(name_ptr)!=0 name_ptr++ name_ptr++ - visible_names[MAX_LINES-1] = name_ptr + filename_ptrs+=2 + } + pokew(filename_ptrs, 0) + } - ; print that new name at the bottom of the list - txt.plot(DIALOG_TOPX+2, DIALOG_TOPY+6+MAX_LINES-1) - txt.print(name_ptr) + sub print_filename(uword name) { + repeat 28 { ; maximum length displayed + cx16.r0L = @(name) + if_z + break + if strings.isprint(cx16.r0L) ; filter out control characters + txt.chrout(@(name)) + else + txt.chrout('?') + name++ + } + } + + sub scroll_list_forward() { + if top_index+max_lines< num_files { + top_index++ + ; scroll the displayed list up 1 + scroll_txt_up(dialog_topx+2, dialog_topy+6, 28, max_lines, sc:' ') + ; print new name at the bottom of the list + txt.plot(dialog_topx+2, dialog_topy+6+max_lines-1) + print_filename(peekw(filename_ptrs_start + (top_index+ selected_line)*$0002)) } } @@ -195,22 +226,10 @@ fileselector { if top_index>0 { top_index-- ; scroll the displayed list down 1 - scroll_txt_down(DIALOG_TOPX+2, DIALOG_TOPY+6, 28, MAX_LINES, sc:' ') - ; shift the name pointer array 1 to the right - for y in MAX_LINES-1 downto 1 - visible_names[y]=visible_names[y-1] - - ; set name pointer array element 0 with newly appeared name - name_ptr = visible_names[0]-2 - while name_ptr>filenamesbuf and @(name_ptr)!=0 - name_ptr-- - if @(name_ptr)==0 - name_ptr++ - visible_names[0] = name_ptr - - ; print that new name at the top of the list - txt.plot(DIALOG_TOPX+2, DIALOG_TOPY+6) - txt.print(name_ptr) + scroll_txt_down(dialog_topx+2, dialog_topy+6, 28, max_lines, sc:' ') + ; print new name at the top of the list + txt.plot(dialog_topx+2, dialog_topy+6) + print_filename(peekw(filename_ptrs_start + top_index * $0002)) } } @@ -244,16 +263,16 @@ fileselector { } sub print_up_and_down() { - if num_files<=MAX_LINES + if num_files<=max_lines return print_up_indicator(top_index>0) - print_down_indicator(num_visible_files < num_files) + print_down_indicator(top_index + num_visible_files < num_files) } sub print_up_indicator(bool shown) { - txt.plot(DIALOG_TOPX, DIALOG_TOPY+5) + txt.plot(dialog_topx, dialog_topy+5) txt.chrout('│') - txt.column(30) + txt.column(dialog_topx+26) if shown txt.print("(up) │\n") else @@ -261,9 +280,9 @@ fileselector { } sub print_down_indicator(bool shown) { - txt.plot(DIALOG_TOPX, DIALOG_TOPY+6+num_visible_files) + txt.plot(dialog_topx, dialog_topy+6+num_visible_files) txt.chrout('│') - txt.column(28) + txt.column(dialog_topx+24) if shown txt.print("(down) │\n") else @@ -281,9 +300,9 @@ fileselector { } sub invert(ubyte line) { - cx16.r1L = DIALOG_TOPY+6+line + cx16.r1L = dialog_topy+6+line ubyte charpos - for charpos in DIALOG_TOPX+1 to DIALOG_TOPX+30 { + for charpos in dialog_topx+1 to dialog_topx+30 { txt.setchr(charpos, cx16.r1L, txt.getchr(charpos, cx16.r1L) ^ 128) } } diff --git a/examples/cx16/fileselector.p8 b/examples/cx16/fileselector.p8 index d250be9f5..7dfde99d2 100644 --- a/examples/cx16/fileselector.p8 +++ b/examples/cx16/fileselector.p8 @@ -1,19 +1,24 @@ %import diskio %import textio +%import sorting +%import strings %zeropage basicsafe -; A "GUI" for an interactive file selector, that scrolls the selection list if it doesn't fit on the screen. +; A "TUI" for an interactive file selector, that scrolls the selection list if it doesn't fit on the screen. +; Depends a lot on diskio routines, and uses the drive set in the diskio.drivenumber variable (usually just 8) -; TODO sort entries alphabetically -; TODO also show directories (how to distinguish them? what with the result value?) +; TODO also show directories (how to distinguish them? what with the result value? start with a slash , so they sort together too?) ; TODO joystick control? mouse control? -; TODO keyboard typing; jump to the first entry that starts with that character +; TODO keyboard typing; jump to the first entry that starts with that character? main { sub start() { - uword chosen = showdir.show(8, 2) ; use hiram bank 2 for buffers + fileselector.configure(20, 10, 20, 2) + uword chosen = fileselector.select("*") + txt.nl() + txt.print_ub(cx16.getrambank()) txt.nl() if chosen!=0 { txt.print("chosen: ") @@ -26,91 +31,103 @@ main { } } -showdir { +fileselector { - const uword filenamesbuf = $a000 ; use HIRAM bank - const uword filenamesbuf_size = $2000 - const ubyte DIALOG_TOPX = 4 - const ubyte DIALOG_TOPY = 2 - const ubyte MAX_LINES = 20 + const uword filenamesbuffer = $a000 ; use a HIRAM bank + const uword filenamesbuf_size = $1e00 ; leaves room for a 256 entry string pointer table at $be00-$bfff + const uword filename_ptrs_start = $be00 ; array of 256 string pointers for each of the names in the buffer. ends with $0000. + ubyte dialog_topx = 4 + ubyte dialog_topy = 10 + ubyte max_lines = 20 + ubyte buffer_rambank = 1 ; default hiram bank to use for the data buffers str chosen_filename = "?" * 32 uword name_ptr ubyte num_visible_files - sub show(ubyte drive, ubyte rambank) -> uword { + + sub configure(ubyte column, ubyte row, ubyte max_entries, ubyte rambank) { + dialog_topx = column + dialog_topy = row + max_lines = max_entries + buffer_rambank = rambank + } + + sub select(str pattern) -> uword { ubyte old_bank = cx16.getrambank() - cx16.rambank(rambank) + cx16.rambank(buffer_rambank) defer cx16.rambank(old_bank) +; if pattern!=0 and pattern[0]==0 +; pattern = 0 ; force pattern to be 0 instead of empty string, to be compatible with prog8 11.0 or older + num_visible_files = 0 chosen_filename[0] = 0 - diskio.drivenumber = drive name_ptr = diskio.diskname() if name_ptr==0 or cbm.READST()!=0 return 0 txt.cls() - txt.plot(DIALOG_TOPX, DIALOG_TOPY) + txt.plot(dialog_topx, dialog_topy) txt.print("┌") linepart() txt.print("┐\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("│ drive ") txt.print_ub(diskio.drivenumber) txt.print(": '") txt.print(name_ptr) txt.chrout('\'') - txt.column(DIALOG_TOPX+31) + txt.column(dialog_topx+31) txt.print("│\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("│ scanning directory... │\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) footerline() - ubyte num_files = diskio.list_filenames(0, filenamesbuf, filenamesbuf_size) ; use Hiram bank to store the files + ubyte num_files = diskio.list_filenames(pattern, filenamesbuffer, filenamesbuf_size) ; use Hiram bank to store the files ubyte selected_line ubyte top_index - uword[MAX_LINES] visible_names + uword filename_ptrs - txt.plot(DIALOG_TOPX+2, DIALOG_TOPY+2) + construct_name_ptr_array() + ; sort alphabetically + sorting.shellsort_pointers(filename_ptrs_start, num_files, sorting.string_comparator) + + ; initial display + txt.plot(dialog_topx+2, dialog_topy+2) txt.print("select file: (") txt.print_ub(num_files) txt.print(" total)") - txt.column(DIALOG_TOPX+31) + txt.column(dialog_topx+31) txt.print("│\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("│ stop or q to abort |\n") - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) txt.print("├") linepart() txt.print("┤\n") print_up_indicator(false) - name_ptr = filenamesbuf - for selected_line in 0 to min(MAX_LINES, num_files)-1 { - if cbm.STOP2() break - - txt.column(DIALOG_TOPX) - txt.print("│ ") - - visible_names[selected_line] = name_ptr ; keep pointer to name - while @(name_ptr)!=0 { - txt.chrout(@(name_ptr)) - name_ptr++ + if num_files>0 { + for selected_line in 0 to min(max_lines, num_files)-1 { + txt.column(dialog_topx) + txt.print("│ ") + print_filename(peekw(filename_ptrs_start+selected_line*$0002)) + txt.column(dialog_topx+31) + txt.print("│\n") + num_visible_files++ } - txt.column(DIALOG_TOPX+31) + } else { + txt.column(dialog_topx) + txt.print("│ no matches.") + txt.column(dialog_topx+31) txt.print("│\n") - name_ptr++ - num_visible_files++ } print_down_indicator(false) - txt.column(DIALOG_TOPX) + txt.column(dialog_topx) footerline() - - name_ptr = filenamesbuf selected_line = 0 invert(selected_line) - print_up_and_down() repeat { @@ -121,41 +138,48 @@ showdir { when key { 3, 27, 'q' -> return 0 ; STOP or Q aborts (and ESC?) '\n',' ' -> { - void strings.copy(visible_names[selected_line], chosen_filename) - return chosen_filename + if num_files>0 { + void strings.copy(peekw(filename_ptrs_start + (top_index+selected_line)*$0002), chosen_filename) + return chosen_filename + } + return 0 } - '[',130 -> { ; PAGEUP + '[',130,157 -> { ; PAGEUP, cursor left ; previous page of lines invert(selected_line) if selected_line==0 - repeat MAX_LINES scroll_list_backward() + repeat max_lines scroll_list_backward() selected_line = 0 invert(selected_line) print_up_and_down() } - ']',2 -> { ; PAGEDOWN - ; next page of lines - invert(selected_line) - if selected_line == MAX_LINES-1 - repeat MAX_LINES scroll_list_forward() - selected_line = num_visible_files-1 - invert(selected_line) - print_up_and_down() + ']',2,29 -> { ; PAGEDOWN, cursor right + if num_files>0 { + ; next page of lines + invert(selected_line) + if selected_line == max_lines-1 + repeat max_lines scroll_list_forward() + selected_line = num_visible_files-1 + invert(selected_line) + print_up_and_down() + } } 17 -> { ; down - invert(selected_line) - if selected_lineMAX_LINES - scroll_list_forward() - invert(selected_line) - print_up_and_down() + if num_files>0 { + invert(selected_line) + if selected_linemax_lines + scroll_list_forward() + invert(selected_line) + print_up_and_down() + } } 145 -> { ; up invert(selected_line) if selected_line>0 selected_line-- - else if num_files>MAX_LINES + else if num_files>max_lines scroll_list_backward() invert(selected_line) print_up_and_down() @@ -165,25 +189,37 @@ showdir { ubyte x,y - sub scroll_list_forward() { - if top_index+MAX_LINES< num_files { - top_index++ - ; scroll the displayed list up 1 - scroll_txt_up(DIALOG_TOPX+2, DIALOG_TOPY+6, 28, MAX_LINES, sc:' ') - ; shift the name pointer array 1 to the left - for y in 0 to MAX_LINES-2 - visible_names[y]=visible_names[y+1] - - ; set name pointer array last element with newly appeared name - name_ptr = visible_names[MAX_LINES-1] + sub construct_name_ptr_array() { + filename_ptrs = filename_ptrs_start + name_ptr = filenamesbuffer + repeat num_files { + pokew(filename_ptrs, name_ptr) while @(name_ptr)!=0 name_ptr++ name_ptr++ - visible_names[MAX_LINES-1] = name_ptr + filename_ptrs+=2 + } + pokew(filename_ptrs, 0) + } - ; print that new name at the bottom of the list - txt.plot(DIALOG_TOPX+2, DIALOG_TOPY+6+MAX_LINES-1) - txt.print(name_ptr) + sub print_filename(uword name) { + repeat 28 { ; maximum length displayed + if @(name)==0 + break + txt.chrout(128) ; don't print control characters + txt.chrout(@(name)) + name++ + } + } + + sub scroll_list_forward() { + if top_index+max_lines< num_files { + top_index++ + ; scroll the displayed list up 1 + scroll_txt_up(dialog_topx+2, dialog_topy+6, 28, max_lines, sc:' ') + ; print new name at the bottom of the list + txt.plot(dialog_topx+2, dialog_topy+6+max_lines-1) + print_filename(peekw(filename_ptrs_start + (top_index+ selected_line)*$0002)) } } @@ -191,22 +227,10 @@ showdir { if top_index>0 { top_index-- ; scroll the displayed list down 1 - scroll_txt_down(DIALOG_TOPX+2, DIALOG_TOPY+6, 28, MAX_LINES, sc:' ') - ; shift the name pointer array 1 to the right - for y in MAX_LINES-1 downto 1 - visible_names[y]=visible_names[y-1] - - ; set name pointer array element 0 with newly appeared name - name_ptr = visible_names[0]-2 - while name_ptr>filenamesbuf and @(name_ptr)!=0 - name_ptr-- - if @(name_ptr)==0 - name_ptr++ - visible_names[0] = name_ptr - - ; print that new name at the top of the list - txt.plot(DIALOG_TOPX+2, DIALOG_TOPY+6) - txt.print(name_ptr) + scroll_txt_down(dialog_topx+2, dialog_topy+6, 28, max_lines, sc:' ') + ; print new name at the top of the list + txt.plot(dialog_topx+2, dialog_topy+6) + print_filename(peekw(filename_ptrs_start + top_index * $0002)) } } @@ -234,16 +258,16 @@ showdir { } sub print_up_and_down() { - if num_files<=MAX_LINES + if num_files<=max_lines return print_up_indicator(top_index>0) - print_down_indicator(num_visible_files < num_files) + print_down_indicator(top_index + num_visible_files < num_files) } sub print_up_indicator(bool shown) { - txt.plot(DIALOG_TOPX, DIALOG_TOPY+5) + txt.plot(dialog_topx, dialog_topy+5) txt.chrout('│') - txt.column(30) + txt.column(dialog_topx+26) if shown txt.print("(up) │\n") else @@ -251,9 +275,9 @@ showdir { } sub print_down_indicator(bool shown) { - txt.plot(DIALOG_TOPX, DIALOG_TOPY+6+num_visible_files) + txt.plot(dialog_topx, dialog_topy+6+num_visible_files) txt.chrout('│') - txt.column(28) + txt.column(dialog_topx+24) if shown txt.print("(down) │\n") else @@ -271,9 +295,9 @@ showdir { } sub invert(ubyte line) { - cx16.r1L = DIALOG_TOPY+6+line + cx16.r1L = dialog_topy+6+line ubyte charpos - for charpos in DIALOG_TOPX+1 to DIALOG_TOPX+30 { + for charpos in dialog_topx+1 to dialog_topx+30 { txt.setchr(charpos, cx16.r1L, txt.getchr(charpos, cx16.r1L) ^ 128) } } diff --git a/examples/test.p8 b/examples/test.p8 index 414a74e42..8c8811f49 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,56 +1,18 @@ %import textio +%import strings +%import diskio %zeropage basicsafe %option no_sysinit main { sub start() { - uword[8] @nosplit warr - uword[8] @split swarr - - uword @shared ptr = $1000 - const uword cptr = $2000 - txt.print_uwhex(&ptr[2], true) - txt.spc() - txt.print_uwhex(&cptr[2], true) - txt.nl() - - txt.print_uwhex(&warr, true) - txt.spc() - txt.print_uwhex(&warr[2], true) - txt.nl() - - txt.print("addresses of split word array:\n") - txt.print_uwhex(&swarr, true) - txt.spc() - txt.print_uwhex(&swarr, true) - txt.nl() - txt.print("addresses of normal word array:\n") - txt.print_uwhex(&warr, true) - txt.spc() - txt.print_uwhex(&swarr[4], true) - txt.nl() - txt.print("addresses of split word array element 4 via var:\n") - cx16.r0L=4 - txt.print_uwhex(&swarr[cx16.r0L], true) - txt.spc() - txt.print_uwhex(&swarr[cx16.r0L], true) - txt.nl() - txt.print("addresses of normal word array element 4:\n") - txt.print_uwhex(&warr[4], true) - txt.spc() - txt.print_uwhex(&