mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-22 12:30:01 +00:00
started on profile/memory views
This commit is contained in:
parent
8a8638b295
commit
89c3209f09
@ -54,3 +54,9 @@ machines.
|
||||
SWEET16 - Woz's tiny bytecode interpreter on the Apple ][ integer BASIC ROM.
|
||||
Still emcumbered by Apple's copyright for the foreseeable future.
|
||||
http://6502.org/source/interpreters/sweet16.htm
|
||||
|
||||
https://github.com/EtchedPixels/FUZIX/wiki
|
||||
|
||||
gcc6809 - need to check this out
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
33
index.html
33
index.html
@ -75,6 +75,18 @@ div.emulator {
|
||||
background-color: #666;
|
||||
margin-top: 20px auto 0;
|
||||
}
|
||||
div.debugwindow {
|
||||
position:absolute;
|
||||
left:50%;
|
||||
top:0;
|
||||
width:50%;
|
||||
background-color: #666;
|
||||
color: #66ff66;
|
||||
white-space: pre;
|
||||
margin-top: 20px auto 0;
|
||||
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
|
||||
font-size: 10pt;
|
||||
}
|
||||
div.mem_info {
|
||||
position: fixed;
|
||||
left: 51%;
|
||||
@ -83,7 +95,7 @@ div.mem_info {
|
||||
color: #66ff66;
|
||||
white-space: pre;
|
||||
padding: 20px;
|
||||
z-index: 2;
|
||||
z-index: 12;
|
||||
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
|
||||
font-size: 12pt;
|
||||
}
|
||||
@ -226,7 +238,7 @@ div.bitmap_editor {
|
||||
</span>
|
||||
<select id="preset_select" name="">
|
||||
</select>
|
||||
<img id="compile_spinner" src="spinner.gif" height="20em" style="visibility:hidden;margin-left:8px;margin-right:8px">
|
||||
<img id="compile_spinner" src="images/spinner.gif" height="20em" style="visibility:hidden;margin-left:8px;margin-right:8px">
|
||||
<span class="debug_bar" id="debug_bar">
|
||||
<button id="dbg_reset" type="submit" title="Reset and Break"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span></button>
|
||||
<button id="dbg_pause" type="button" title="Pause"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span></button>
|
||||
@ -235,9 +247,13 @@ div.bitmap_editor {
|
||||
<button id="dbg_toline" type="submit" title="Run To Line"><span class="glyphicon glyphicon-save" aria-hidden="true"></span></button>
|
||||
<button id="dbg_stepout" type="submit" title="Step Out of Subroutine"><span class="glyphicon glyphicon-hand-up" aria-hidden="true"></span></button>
|
||||
<button id="dbg_stepback" type="submit" title="Step Backwards"><span class="glyphicon glyphicon-step-backward" aria-hidden="true"></span></button>
|
||||
<button id="dbg_timing" type="submit" title="See Timing" style="display:none"><span class="glyphicon glyphicon-time" aria-hidden="true"></span></button>
|
||||
</span>
|
||||
<button id="dbg_disasm" type="submit" title="Show Disassembly" style="display:none"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></button>
|
||||
<span class="extra_bar" id="extra_bar">
|
||||
<button id="dbg_timing" type="submit" title="See Timing" style="display:none"><span class="glyphicon glyphicon-time" aria-hidden="true"></span></button>
|
||||
<button id="dbg_disasm" type="submit" title="Show Disassembly" style="display:none"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></button>
|
||||
<button id="dbg_memory" type="submit" title="Show Memory" style="display:none"><span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span></button>
|
||||
<button id="dbg_profile" type="submit" title="Show Profile" style="display:none"><span class="glyphicon glyphicon-stats" aria-hidden="true"></span></button>
|
||||
</span>
|
||||
</div>
|
||||
<div id="notebook">
|
||||
<div id="workspace">
|
||||
@ -249,8 +265,12 @@ div.bitmap_editor {
|
||||
<div class="emulator" id="emulator">
|
||||
<div id="javatari-screen" style="margin: 0 auto; box-shadow: 2px 2px 10px rgb(60, 60, 60);"></div>
|
||||
<div id="javatari-console-panel" style="margin: 0 auto; box-shadow: 2px 2px 10px rgb(60, 60, 60);"></div>
|
||||
<div id="mem_info" class="mem_info" style="display:none">
|
||||
</div>
|
||||
</div>
|
||||
<div id="mem_info" class="mem_info" style="display:none">
|
||||
</div>
|
||||
<div id="profileview" class="debugwindow" style="display:none;z-index:10">
|
||||
</div>
|
||||
<div id="memoryview" class="debugwindow" style="display:none;z-index:10">
|
||||
</div>
|
||||
</div>
|
||||
<div class="twitbtn">
|
||||
@ -314,6 +334,7 @@ Dest: $<span id="bitmap_editor_dest">0000/0</span>
|
||||
<script src="FileSaver.js/FileSaver.min.js"></script>
|
||||
<script src="octokat.js/dist/octokat.js"></script>
|
||||
|
||||
<script src="src/vlist.js"></script>
|
||||
<script src="src/emu.js"></script>
|
||||
<script src="src/audio.js"></script>
|
||||
<script src="src/util.js"></script>
|
||||
|
21
src/emu.js
21
src/emu.js
@ -825,11 +825,26 @@ function AddressDecoder(table, options) {
|
||||
}
|
||||
|
||||
var BusProbe = function(bus) {
|
||||
var active = false;
|
||||
var callback;
|
||||
this.activate = function(_callback) {
|
||||
active = true;
|
||||
callback = _callback;
|
||||
}
|
||||
this.deactivate = function() {
|
||||
active = false;
|
||||
callback = null;
|
||||
}
|
||||
this.read = function(a) {
|
||||
var val = bus.read(a);
|
||||
return val;
|
||||
if (active) {
|
||||
callback(a);
|
||||
}
|
||||
return bus.read(a);
|
||||
}
|
||||
this.write = function(a,v) {
|
||||
return bus.write(a,v);
|
||||
if (active) {
|
||||
callback(a,v);
|
||||
}
|
||||
bus.write(a,v);
|
||||
}
|
||||
}
|
||||
|
@ -264,6 +264,9 @@ var GalaxianPlatform = function(mainElement, options) {
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
}
|
||||
this.readMemory = function(a) {
|
||||
return (a == 0x7000 || a == 0x7800) ? null : membus.read; // ignore watchdog
|
||||
};
|
||||
audio = new MasterAudio();
|
||||
psg1 = new AY38910_Audio(audio);
|
||||
psg2 = new AY38910_Audio(audio);
|
||||
@ -368,7 +371,7 @@ var GalaxianPlatform = function(mainElement, options) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
|
@ -60,6 +60,7 @@ var Midway8080BWPlatform = function(mainElement) {
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
this.readMemory = membus.read;
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
addr &= 0x3;
|
||||
@ -177,7 +178,7 @@ var Midway8080BWPlatform = function(mainElement) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
|
@ -122,7 +122,7 @@ var KonamiSoundPlatform = function(mainElement) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
|
@ -137,7 +137,7 @@ var WilliamsSoundPlatform = function(mainElement) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
|
@ -65,7 +65,7 @@ var VCSPlatform = function() {
|
||||
return {x:xpos, y:ypos};
|
||||
}
|
||||
|
||||
this.isRunning = function() { return Javatari.room.console.isRunning(); }
|
||||
this.isRunning = function() { return Javatari.room && Javatari.room.console.isRunning(); }
|
||||
this.pause = function() { Javatari.room.console.pause(); }
|
||||
this.resume = function() { Javatari.room.console.go(); }
|
||||
this.step = function() { Javatari.room.console.debugSingleStepCPUClock(); }
|
||||
|
@ -89,6 +89,7 @@ var AtariVectorPlatform = function(mainElement) {
|
||||
], {gmask:0x7fff})
|
||||
|
||||
};
|
||||
this.readMemory = membus.read;
|
||||
cpu = self.newCPU(bus);
|
||||
// create video/audio
|
||||
video = new VectorVideo(mainElement,1024,1024);
|
||||
@ -131,7 +132,7 @@ var AtariVectorPlatform = function(mainElement) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
@ -275,7 +276,7 @@ var AtariColorVectorPlatform = function(mainElement) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
@ -401,7 +402,7 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
|
@ -15,7 +15,7 @@ var VicDualPlatform = function(mainElement) {
|
||||
this.__proto__ = new BaseZ80Platform();
|
||||
|
||||
var cpu, ram, membus, iobus, rom;
|
||||
var video, audio, psg, timer, pixels;
|
||||
var video, audio, psg, timer, pixels, probe;
|
||||
var inputs = [0xff, 0xff, 0xff, 0xff^0x8]; // most things active low
|
||||
var palbank = 0;
|
||||
|
||||
@ -108,6 +108,7 @@ var VicDualPlatform = function(mainElement) {
|
||||
]),
|
||||
isContended: function() { return false; },
|
||||
};
|
||||
this.readMemory = membus.read;
|
||||
iobus = {
|
||||
read: function(addr) {
|
||||
return inputs[addr&3];
|
||||
@ -119,9 +120,10 @@ var VicDualPlatform = function(mainElement) {
|
||||
if (addr & 0x40) { palbank = val & 3; }; // palette
|
||||
}
|
||||
};
|
||||
probe = new BusProbe(membus);
|
||||
cpu = window.Z80({
|
||||
display: {},
|
||||
memory: membus,
|
||||
memory: probe,
|
||||
ioBus: iobus
|
||||
});
|
||||
video = new RasterVideo(mainElement,256,224,{rotate:-90});
|
||||
@ -199,7 +201,7 @@ var VicDualPlatform = function(mainElement) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
@ -223,6 +225,7 @@ var VicDualPlatform = function(mainElement) {
|
||||
layers: {width:256, height:224, tiles:[]}
|
||||
} : null;
|
||||
}
|
||||
this.getProbe = function() { return probe; }
|
||||
}
|
||||
|
||||
PLATFORMS['vicdual'] = VicDualPlatform;
|
||||
|
@ -278,6 +278,7 @@ var WilliamsPlatform = function(mainElement, proto) {
|
||||
read: memread_williams,
|
||||
write: memwrite_williams,
|
||||
};
|
||||
this.readMemory = membus.read;
|
||||
//var probebus = new BusProbe(membus);
|
||||
var iobus = {
|
||||
read: function(a) {return 0;},
|
||||
@ -384,7 +385,7 @@ var WilliamsPlatform = function(mainElement, proto) {
|
||||
}
|
||||
|
||||
this.isRunning = function() {
|
||||
return timer.isRunning();
|
||||
return timer && timer.isRunning();
|
||||
}
|
||||
this.pause = function() {
|
||||
timer.stop();
|
||||
|
158
src/ui.js
158
src/ui.js
@ -115,12 +115,10 @@ var current_preset_index = -1; // TODO: use URL
|
||||
var current_preset_id = null;
|
||||
var assemblyfile = null;
|
||||
var sourcefile = null;
|
||||
var pcvisits;
|
||||
var trace_pending_at_pc;
|
||||
var store;
|
||||
var pendingWorkerMessages = 0;
|
||||
var editor;
|
||||
|
||||
var disasmview = CodeMirror(document.getElementById('disassembly'), {
|
||||
mode: 'z80',
|
||||
theme: 'cobalt',
|
||||
@ -129,6 +127,9 @@ var disasmview = CodeMirror(document.getElementById('disassembly'), {
|
||||
styleActiveLine: true
|
||||
});
|
||||
|
||||
var memoryview;
|
||||
var profileview;
|
||||
|
||||
function newEditor(mode) {
|
||||
var isAsm = (mode != 'text/x-csrc');
|
||||
editor = CodeMirror(document.getElementById('editor'), {
|
||||
@ -147,6 +148,11 @@ function newEditor(mode) {
|
||||
setCode(editor.getValue());
|
||||
}, 200);
|
||||
});
|
||||
editor.on('scroll', function(ed, changeobj) {
|
||||
if (profileview) {
|
||||
profileview.container.scrollTop = editor.getScrollInfo().top;
|
||||
}
|
||||
});
|
||||
editor.setOption("mode", mode);
|
||||
}
|
||||
|
||||
@ -400,7 +406,8 @@ function setCompileOutput(data) {
|
||||
platform.loadROM(getCurrentPresetTitle(), rom);
|
||||
resume();
|
||||
current_output = rom;
|
||||
pcvisits = {};
|
||||
prof_reads = [];
|
||||
prof_writes = [];
|
||||
toolbar.removeClass("has-errors");
|
||||
} catch (e) {
|
||||
console.log(e); // TODO: show error
|
||||
@ -447,7 +454,6 @@ function setCompileOutput(data) {
|
||||
worker.onmessage = function(e) {
|
||||
toolbar.removeClass("is-busy");
|
||||
$('#compile_spinner').css('visibility', 'hidden');
|
||||
// TODO: this doesn't completely work yet
|
||||
if (pendingWorkerMessages > 1) {
|
||||
pendingWorkerMessages = 0;
|
||||
setCode(editor.getValue());
|
||||
@ -523,7 +529,6 @@ function setupBreakpoint() {
|
||||
console.log("BREAKPOINT", hex(PC));
|
||||
// TODO: switch to disasm
|
||||
}
|
||||
pcvisits[PC] = pcvisits[PC] ? pcvisits[PC]+1 : 1;
|
||||
showMemory(state);
|
||||
updateDisassembly();
|
||||
});
|
||||
@ -872,6 +877,136 @@ function _breakExpression() {
|
||||
}
|
||||
}
|
||||
|
||||
function updateDebugWindows() {
|
||||
if (platform.isRunning()) {
|
||||
updateMemoryWindow();
|
||||
updateProfileWindow();
|
||||
}
|
||||
setTimeout(updateDebugWindows, 200);
|
||||
}
|
||||
|
||||
function getProfileLine(line) {
|
||||
var offset = sourcefile.line2offset[line];
|
||||
if (offset >= 0) {
|
||||
if (prof_reads[offset] > 0)
|
||||
return ""+prof_reads[offset];
|
||||
}
|
||||
}
|
||||
|
||||
function updateProfileWindow() {
|
||||
if (profileview && sourcefile) {
|
||||
$("#profileview").find('[data-index]').each(function(i,e) {
|
||||
var div = $(e);
|
||||
var lineno = div.attr('data-index') | 0;
|
||||
var newtext = getProfileLine(lineno+1);
|
||||
if (newtext) {
|
||||
var oldtext = div.text();
|
||||
if (oldtext != newtext)
|
||||
div.text(newtext);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMemoryWindow() {
|
||||
if (memoryview) {
|
||||
$("#memoryview").find('[data-index]').each(function(i,e) {
|
||||
var div = $(e);
|
||||
var offset = div.attr('data-index') * 16;
|
||||
var oldtext = div.text();
|
||||
var newtext = getMemoryLineAtOffset(offset);
|
||||
if (oldtext != newtext)
|
||||
div.text(newtext);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getMemoryLineAtOffset(offset) {
|
||||
var s = hex(offset,4) + ' ';
|
||||
for (var i=0; i<16; i++) {
|
||||
var read = platform.readMemory(offset+i);
|
||||
if (i==8) s += ' ';
|
||||
s += ' ' + (read>=0?hex(read,2):'??');
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function getEditorLineHeight() {
|
||||
return $("#editor").find(".CodeMirror-line").first().height();
|
||||
}
|
||||
|
||||
function showMemoryWindow() {
|
||||
memoryview = new VirtualList({
|
||||
w:$("#emulator").width(),
|
||||
h:$("#emulator").height(),
|
||||
itemHeight: getEditorLineHeight(),
|
||||
totalRows: 0x1000,
|
||||
generatorFn: function(row) {
|
||||
var s = getMemoryLineAtOffset(row * 16);
|
||||
var div = document.createElement("div");
|
||||
div.appendChild(document.createTextNode(s));
|
||||
return div;
|
||||
}
|
||||
});
|
||||
$("#memoryview").empty().append(memoryview.container);
|
||||
updateMemoryWindow();
|
||||
memoryview.scrollToItem(0x800); // TODO
|
||||
}
|
||||
|
||||
function toggleMemoryWindow() {
|
||||
if ($("#profileview").is(':visible')) toggleProfileWindow();
|
||||
if ($("#memoryview").is(':visible')) {
|
||||
memoryview = null;
|
||||
$("#emulator").show();
|
||||
$("#memoryview").hide();
|
||||
} else {
|
||||
showMemoryWindow();
|
||||
$("#emulator").hide();
|
||||
$("#memoryview").show();
|
||||
}
|
||||
}
|
||||
|
||||
function createProfileWindow() {
|
||||
profileview = new VirtualList({
|
||||
w:$("#emulator").width(),
|
||||
h:$("#emulator").height(),
|
||||
itemHeight: getEditorLineHeight(),
|
||||
totalRows: editor.lineCount(),
|
||||
generatorFn: function(row) {
|
||||
var div = document.createElement("div");
|
||||
div.appendChild(document.createTextNode("."));
|
||||
return div;
|
||||
}
|
||||
});
|
||||
$("#profileview").empty().append(profileview.container);
|
||||
updateProfileWindow();
|
||||
}
|
||||
|
||||
var prof_reads, prof_writes;
|
||||
|
||||
function profileWindowCallback(a,v) {
|
||||
if (v >= 0) {
|
||||
prof_writes[a] = (prof_writes[a]|0)+1;
|
||||
} else {
|
||||
prof_reads[a] = (prof_reads[a]|0)+1;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleProfileWindow() {
|
||||
if ($("#memoryview").is(':visible')) toggleMemoryWindow();
|
||||
if ($("#profileview").is(':visible')) {
|
||||
profileview = null;
|
||||
platform.getProbe().deactivate();
|
||||
$("#emulator").show();
|
||||
$("#profileview").hide();
|
||||
} else {
|
||||
createProfileWindow();
|
||||
platform.getProbe().activate(profileWindowCallback);
|
||||
$("#emulator").hide();
|
||||
$("#profileview").show();
|
||||
}
|
||||
}
|
||||
|
||||
function setupDebugControls(){
|
||||
$("#dbg_reset").click(resetAndDebug);
|
||||
$("#dbg_pause").click(pause);
|
||||
@ -883,6 +1018,12 @@ function setupDebugControls(){
|
||||
if (platform_id == 'vcs') {
|
||||
$("#dbg_timing").click(traceTiming).show();
|
||||
}
|
||||
else if (platform.readAddress) {
|
||||
$("#dbg_memory").click(toggleMemoryWindow).show();
|
||||
}
|
||||
if (platform.getProbe) {
|
||||
$("#dbg_profile").click(toggleProfileWindow).show();
|
||||
}
|
||||
if (platform.saveState) { // TODO: only show if listing or disasm available
|
||||
$("#dbg_disasm").click(toggleDisassembly).show();
|
||||
}
|
||||
@ -893,6 +1034,7 @@ function setupDebugControls(){
|
||||
$("#item_reset_file").click(_resetPreset);
|
||||
$("#item_debug_expr").click(_breakExpression);
|
||||
$("#item_download_rom").click(_downloadROMImage);
|
||||
updateDebugWindows();
|
||||
}
|
||||
|
||||
function showWelcomeMessage() {
|
||||
@ -938,11 +1080,6 @@ function showWelcomeMessage() {
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
function setupBitmapEditor() {
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
var qs = (function (a) {
|
||||
if (!a || a == "")
|
||||
return {};
|
||||
@ -975,7 +1112,6 @@ function startPlatform() {
|
||||
// start platform and load file
|
||||
preloadWorker(qs['file']);
|
||||
setupDebugControls();
|
||||
setupBitmapEditor();
|
||||
platform.start();
|
||||
loadPreset(qs['file']);
|
||||
updateSelector();
|
||||
|
161
src/vlist.js
Normal file
161
src/vlist.js
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2013 Sergi Mansilla
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the 'Software'), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Creates a virtually-rendered scrollable list.
|
||||
* @param {object} config
|
||||
* @constructor
|
||||
*/
|
||||
function VirtualList(config) {
|
||||
var width = (config && config.w + 'px') || '100%';
|
||||
var height = (config && config.h + 'px') || '100%';
|
||||
var itemHeight = this.itemHeight = config.itemHeight;
|
||||
|
||||
this.items = config.items;
|
||||
this.generatorFn = config.generatorFn;
|
||||
this.totalRows = config.totalRows || (config.items && config.items.length);
|
||||
|
||||
var scroller = VirtualList.createScroller(itemHeight * this.totalRows);
|
||||
this.container = VirtualList.createContainer(width, height);
|
||||
this.container.appendChild(scroller);
|
||||
|
||||
var screenItemsLen = Math.ceil(config.h / itemHeight);
|
||||
// Cache 4 times the number of items that fit in the container viewport
|
||||
this.cachedItemsLen = screenItemsLen * 3;
|
||||
this._renderChunk(this.container, 0);
|
||||
|
||||
var self = this;
|
||||
var lastRepaintY;
|
||||
var maxBuffer = screenItemsLen * itemHeight;
|
||||
var lastScrolled = 0;
|
||||
|
||||
// As soon as scrolling has stopped, this interval asynchronouslyremoves all
|
||||
// the nodes that are not used anymore
|
||||
this.rmNodeInterval = setInterval(function() {
|
||||
if (Date.now() - lastScrolled > 100) {
|
||||
var badNodes = document.querySelectorAll('[data-rm="1"]');
|
||||
for (var i = 0, l = badNodes.length; i < l; i++) {
|
||||
try {
|
||||
self.container.removeChild(badNodes[i]);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
|
||||
function onScroll(e) {
|
||||
var scrollTop = e.target.scrollTop; // Triggers reflow
|
||||
if (!lastRepaintY || Math.abs(scrollTop - lastRepaintY) > maxBuffer) {
|
||||
var first = parseInt(scrollTop / itemHeight) - screenItemsLen;
|
||||
self._renderChunk(self.container, first < 0 ? 0 : first);
|
||||
lastRepaintY = scrollTop;
|
||||
}
|
||||
|
||||
lastScrolled = Date.now();
|
||||
e.preventDefault && e.preventDefault();
|
||||
}
|
||||
|
||||
this.container.addEventListener('scroll', onScroll);
|
||||
}
|
||||
|
||||
VirtualList.prototype.createRow = function(i) {
|
||||
var item;
|
||||
if (this.generatorFn)
|
||||
item = this.generatorFn(i);
|
||||
else if (this.items) {
|
||||
if (typeof this.items[i] === 'string') {
|
||||
var itemText = document.createTextNode(this.items[i]);
|
||||
item = document.createElement('div');
|
||||
item.style.height = this.itemHeight + 'px';
|
||||
item.appendChild(itemText);
|
||||
} else {
|
||||
item = this.items[i];
|
||||
}
|
||||
}
|
||||
|
||||
item.classList.add('vrow');
|
||||
item.setAttribute('data-index', ''+i);
|
||||
item.style.position = 'absolute';
|
||||
item.style.top = (i * this.itemHeight) + 'px';
|
||||
return item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a particular, consecutive chunk of the total rows in the list. To
|
||||
* keep acceleration while scrolling, we mark the nodes that are candidate for
|
||||
* deletion instead of deleting them right away, which would suddenly stop the
|
||||
* acceleration. We delete them once scrolling has finished.
|
||||
*
|
||||
* @param {Node} node Parent node where we want to append the children chunk.
|
||||
* @param {Number} from Starting position, i.e. first children index.
|
||||
* @return {void}
|
||||
*/
|
||||
VirtualList.prototype._renderChunk = function(node, from) {
|
||||
var finalItem = from + this.cachedItemsLen;
|
||||
if (finalItem > this.totalRows)
|
||||
finalItem = this.totalRows;
|
||||
|
||||
// Append all the new rows in a document fragment that we will later append to
|
||||
// the parent node
|
||||
var fragment = document.createDocumentFragment();
|
||||
for (var i = from; i < finalItem; i++) {
|
||||
fragment.appendChild(this.createRow(i));
|
||||
}
|
||||
|
||||
// Hide and mark obsolete nodes for deletion.
|
||||
for (var j = 1, l = node.childNodes.length; j < l; j++) {
|
||||
node.childNodes[j].style.display = 'none';
|
||||
node.childNodes[j].setAttribute('data-rm', '1');
|
||||
}
|
||||
node.appendChild(fragment);
|
||||
};
|
||||
|
||||
VirtualList.createContainer = function(w, h) {
|
||||
var c = document.createElement('div');
|
||||
c.style.width = w;
|
||||
c.style.height = h;
|
||||
c.style.overflow = 'auto';
|
||||
c.style.position = 'relative';
|
||||
c.style.padding = 0;
|
||||
c.style.border = '1px solid black';
|
||||
return c;
|
||||
};
|
||||
|
||||
VirtualList.createScroller = function(h) {
|
||||
var scroller = document.createElement('div');
|
||||
scroller.style.opacity = 0;
|
||||
scroller.style.position = 'absolute';
|
||||
scroller.style.top = 0;
|
||||
scroller.style.left = 0;
|
||||
scroller.style.width = '1px';
|
||||
scroller.style.height = h + 'px';
|
||||
return scroller;
|
||||
};
|
||||
|
||||
VirtualList.prototype.scrollToItem = function(index) {
|
||||
this.container.scrollTop = this.itemHeight * index;
|
||||
};
|
Loading…
Reference in New Issue
Block a user