Yeah, there are ways to change the color of the scrollbar, but it just doesn't have that professional look that a real interface needs if your game is ever to be noteworthy outside of BYOND.
Let's take a look at the image format.
In order to create your own scrollbar, you will need to put together some artwork of your own.
You can feel free to modify the HTML and Javascript I've put together for you guys, but I think I've got my system set up so that you can set up a reasonably flexible chat panel with some slight modification to the CSS in the HTML document you are going to be sending to your client.
Pretty artwork
In the below image, I have the panel background I've decided to use, the scrollbar track background, the scroll buttons (up and down), and the scroll handle theme (top, middle, bottom)
A few notes here. None of the dimensions in the below image are required. Due to the entire system being built in HTML/CSS, you can pretty much customize your elements to look however you like.
One feature I didn't implement were segmented tracks, meaning the track has to be either one repeating, one scaled, or one solid image. ...Unless you want to adapt the JS to account for a resizing cap (I might later).
Now that we've got our artwork, let's take a look at setting up the HTML.
Enter the Browser
Our document consists of three elements:
the script,
the style,
and the body.
Note that not all scripts need to be in the header. Scripts are loaded and executed in the order that they are found by the browser within the file. This means that some scripts need to be lower in the document than others.
The header:
<HTML>
<HEAD>
<style type="text/css">
body {
background-image:url('chatbg.png');
background-repeat:no-repeat;
background-attachment:scroll;
overflow: hidden;
margin: 0px;
border: 0px;
padding: 0px;
}
#scrollbar_container {
position:absolute;
width:316px;
}
#scrollbar_track {
position:absolute;
top:16px;
right:0px;
height:96px;
width:16px;
background-image:url('chatscrollbar-bg.png');
cursor:move;
}
#scrollbar_handle {
width:14px;
left:1px;
overflow:auto;
background-image:url('chatscroller-m.png');
cursor:move;
}
#scrollbar_content {
overflow:hidden;
width:300px;
height:128px;
}
.scrollbart {
position:relative;
width:14px;
height:50%;
overflow:auto;
background-image:url('chatscroller-t.png');
background-repeat:no-repeat;
cursor:move;
border:0px;
padding:0px;
margin:0px;
}
.scrollbarb {
position:relative;
width:14px;
height:50%;
overflow:auto;
background-image:url('chatscroller-b.png');
background-repeat:no-repeat;
background-position:left bottom;
cursor:move;
border:0px;
padding:0px;
margin:0px;
}
#scrollbtnup {
width:14px;
height:14px;
position:absolute;
top:1px;
right:1px;
overflow:auto;
background-image:url('chatscrollbar-scrollup.png');
}
#scrollbtndown {
width:14px;
height:14px;
position:absolute;
bottom:1px;
right:1px;
overflow:hidden;
background-image:url('chatscrollbar-scrolldown.png');
}
</style>
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="scriptaculous.js"></script>
<script type="text/javascript" src="effects.js"></script>
<script type="text/javascript" src="controls.js"></script>
<script type="text/javascript" src="slider.js"></script>
<script type="text/javascript" src="livepipe.js"></script>
<script type="text/javascript" src="scrollbar.js"></script>
</HEAD>
You'll notice that the CSS requires unique names for many of the elements on the page. (# denotes a unique name, where . denotes a class)
The important unique names to know:
#scrollbar_container: holds the entire scrollbar and content panel.
#scrollbar_track: the track for the scrollbar being defined.
#scrollbar_handle: the handle for the scrollbar and container for the scrollbar caps.
#scrollbar_content: This object holds the content area we are scrolling around in.
#scrollbtnup: This is a button that will be tied to the scrollbar triggering a scroll up command.
#scrollbtndown: This is a button that will be tied to the scrollbar triggering a scroll down command.
And the important classes to know:
.scrollbart
.scrollbarb
Both the above classes are set inside the scrollbar handle, and will show the cap images for the handle.
You'll also notice a number of javascript files included. This is the part of the livepipe javascript library we're implementing in our project. You can check out livepipe here: http://livepipe.net/
The last one is "scrollbar.js". I've made some modifications to the file myself, so you will have to snag a copy of it from here. (Right click and save as!)
Note: If the above link is dead, that probably means my membership expired, so you can grab the file from the demo here.
The body:
<BODY>
<div id="scrollbar_container">
<div id="scrollbtnup"></div>
<div id="scrollbar_track"><div id="scrollbar_handle"><div class="scrollbart"></div><div class="scrollbarb"></div></div></div>
<div id="scrollbar_content"></div>
<div id="scrollbtndown"></div>
</div>
The scrollbar class expects a particular formatting of the DIVS included here. You can do pretty much what you want with the buttons, they are located by name, but the other elements need to be exactly in these positions, or you are going to have inconsistent results.
Also in the body, you will find some javascript:
<script type="text/javascript">
var scrollbar = new Control.ScrollBar('scrollbar_content','scrollbar_track','scrollbtnup','scrollbtndown');
var cureval = 0;
var scrollup = function(){scrollbar.scrollBy(-24);};
var scrolldown = function(){scrollbar.scrollBy(24);};
The above sets up a new scrollbar and some variables that will help us process some behavior of the scrollbar.
$('scrollbtndown').observe('mousedown',function(event){
scrollbar.scrollBy(24);
if(cureval!=0)
{
window.self.clearInterval(cureval);
}
cureval = window.self.setInterval(scrolldown,250);
event.stop();
});
$('scrollbtndown').observe('mouseup',function(event){
if(cureval!=0)
{
window.self.clearInterval(cureval);
}
event.stop();
});
These two functions handle mouse events on the down button for scrolling the scrollbar. Basically, they just setup up a repeated function to be processed every half a second, and clear it when a button is released.
$('scrollbtnup').observe('mousedown',function(event){
scrollbar.scrollBy(-24);
if(cureval!=0)
{
window.self.clearInterval(cureval);
}
cureval = window.self.setInterval(scrollup,250);
event.stop();
});
$('scrollbtnup').observe('mouseup',function(event){
if(cureval!=0)
{
window.self.clearInterval(cureval);
}
event.stop();
});
The above is basically the same, but for the up button.
var elem = document.getElementById('scrollbar_content');
function receiveMessage(msgtext)
{
var oldScroll = scrollbar.slider.value;
elem.innerHTML = elem.innerHTML + msgtext + "<BR>";
scrollbar.recalculateLayout();
if(oldScroll==1)
{
window.self.setTimeout(function(){scrollbar.slider.setValue(1);},100);
}
}
scrollbar.slider.setValue(1);
</script>
</BODY>
</HTML>
The above function is actually a means for getting messages from BYOND itself. This function can be called directly from DM code using a new-ish feature of the output() command. We'll cover invoking it later, but this function inserts some HTML directly into the content area.
The function also checks to see if the client has their scrollbar all the way to the bottom, which will automatically update the position of the content area once a new message is received.
There's a delayed function in there that is pretty important. Updating the position of the scroller too quickly in a row can cause the slider to draw incorrectly. This is a bug with LivePipe's slider code that I wasn't able/willing to diagnose and fix. The delay is a nice workaround, and you shouldn't notice it too much.
You'll also notice in one of the last lines there, we set the scrollbar to the bottom of its slider, that way the first time the output is received that pushes down the scroll area, you actually wind up not having to manually scroll down.
Okay, well, that's it for our chatpanel HTML file. Save it in your project folder, and remember the name.
Putting it together
Now that we have our artwork, javascript, CSS and our HTML, we need to put all these files in your project directory.
Let's move these files over to the client when they connect, that way we can just set up the browser whenever we are ready.
client
proc
init_scrollbars()
//send the client our scrollbar images
src << browse('chatbg.png',"display=0;")
src << browse('chatscrollbar-bg.png',"display=0;")
src << browse('chatscrollbar-scrolldown.png',"display=0;")
src << browse('chatscrollbar-scrollup.png',"display=0;")
src << browse('chatscroller-t.png',"display=0;")
src << browse('chatscroller-m.png',"display=0;")
src << browse('chatscroller-b.png',"display=0;")
//send the client the livepipe js package
src << browse('scriptaculous.js',"display=0;")
src << browse('effects.js',"display=0;")
src << browse('prototype.js',"display=0;")
src << browse('controls.js',"display=0;")
src << browse('slider.js',"display=0;")
src << browse('livepipe.js',"display=0;")
src << browse('scrollbar.js',"display=0;")
New()
. = ..()
spawn()
init_scrollbars()
This code sends all the required files to the client's cache. These files will persist at least until the connection to the server ends, so you should only have to send them once when the client connects, or when you first initialize your chat panel.
Now, go into your interface file, add a browser, and name it.
Now that you have your browser panel done, let's modify client/New() a bit, and send him our HTML file.
client
New()
. = ..()
spawn()
init_scrollbars()
src << browse('chatpanel.html',"window=chatpanel")
Once you have that done, you can send some messages to javascript however you would like.
It works like this:
output(msg,"[browser_id]:[javascriptFunction]")
You'll want to get msg from a params list generated by list2params, the browser_id is the id of the browser you created in your interface file, and the javascriptFunction is going to be the name of the function we set up in Javascript earlier in our HTML file (I used receiveMessage).
Personally, I like binding a verb to an input element in the dmf file, and outputting the message to the browser. I've also built you a nice little convenience function that will help you with message output to multiple clients/mobs at once.
proc
chatmessage(var/c,var/msg,var/browsername="defaultbrowser")
var/ms = list2params(list(msg))
if(c==world||c==null)
if(browsername=="defaultbrowser")
for(var/client/cl in world)
cl << output(ms,"[cl.default_browser]:receiveMessage")
else
c << output(ms,"[browsername]:receiveMessage")
else if(istype(c,/mob))
var/mob/m = c
var/client/cl = m.client
if(cl)
if(browsername=="defaultbrowser")
cl << output(ms,"[cl.default_browser]:receiveMessage")
else
cl << output(ms,"[browsername]:receiveMessage")
else if(istype(c,/client))
var/client/cl = c
if(browsername=="defaultbrowser")
cl << output(ms,"[cl.default_browser]:receiveMessage")
else
cl << output(ms,"[browsername]:receiveMessage")
else if(istype(c,/list))
for(var/v in c)
.(v,msg,browsername)
client
var
default_browser = "chatpanel"
verb
msginput(msg as text|null)
if(msg!=null)
chatmessage(world,"[src.mob.name]: [msg]","chatpanel")
The above code also includes a default browser variable for the client, so that you can set a browser as the default for input, allowing clients to change browsers and receive messages wherever the variable happens to be pointing to.
Make sure you set an input element to have a command of "msginput", otherwise you'll have to use a verb panel, and that makes Ter cry.
Notes:
There are a few things to take note of that I have done in my attempt to get this system set up for my needs.
1) I intended to use this system with an even-only sized handle. There is a bit of code marked by comments in the recalculateLayout function in scrollbar.js. Change the bit that checks if the size of the handle is odd.
2) The current way in which the scrollbar handles show their graphics does not allow for transparency. You will have to add a third element to the handle container, and change the commented code in recalculateLayout to account for a 3-object adjustment rather than a 2-object adjustment.
3) The current way in which the scrollbar track is formatted does not allow cap-background stretching like the handle. If you want to do this, again, the location to handle it will be in recalculateLayout.
4) The scroll buttons might need some sort of velocity modification before this sees real use. You would edit the functions stored in scrollup/scrolldown to handle any sort of velocity.
5) The current setup allows the player to enter unsanitized HTML. This is probably not what you want. You are definitely going to want to escape the HTML tags the player enters into the input before you send it off to the browser, or bad things can happen. You've been warned.
This will be useful for many.