silbinarywolf
silbinarywolf
Silbinary Wolf
8 posts
Full-time full-stack developer based in Melbourne, Australia. Hobby game developer and amateur TV binger.
Don't wanna be here? Send us removal request.
silbinarywolf · 4 years ago
Text
Lookahead Dreamdust
I look out the window and gaze upon a washed out grass knoll sits within a white space. Sitting outside the knoll is a tree rooted in the empty space, all the roots, normally concealed by the grass are visible and winding as if it were constrained dirt.
My chest is still pumping as I rapidly sit down against the tree. “Wait. When did I get outside?”. I stare down and enjoy the feeling of grass between my toes, it didn’t really matter that my shoes were still on. The wind felt soft against my body and the grass tickled my toes and legs. Still, I felt tense and uncomfortable, something was wrong. Was it just me?
As I started to get up on my feet, the sky flicked from a clear sky blue to a dark star filled sky that you could only see in a regional town. The night was chilly and filled with fog and the grass felt wet and muddy as I pressed myself up off of the ground. I hated how my hands felt, muddy and slimy. I started wiping them on my clothes, which filled me with a shame that wasn’t familiar, so I stopped, leaving me in an uncomfortable middle-ground.
I ran towards the head-building infront of me. I observed my feet stomping up the 10 steps from the perspective of a film director where I could see a candy wrapper be crushed and dragged along the sole of my feet, propelled to the left out of frame, while my legs moved right. I felt a sudden urgency to get inside the building, I burst through the double doors with both hands and shut them behind me. I thought this entrance was a motion-sensored sliding door? I looked behind me and saw a purple wave grow brighter in the fog. There was a sudden flash and purple high pressure steam pressed against the building but didn’t seem to be getting in.
I turned around to find the hallway to be incredibly well lit, lined with high-school lockers symmetrically down the hall, their colours creating a pastel rainbow colour as the lockers shift color every 5 lockers. The room felt bright and saturated at the same time. I walked down towards my locker. My shoes creating 2 distinct sounds per step, clip-clop, clip-clop, clip-clop. The clops echoed throughout the hall while the clips muted rapidly and rather jarringly.
I walked up to my locker, it was a pastel purple with a distinct marking, a cross, etched into the metal just to the right of the vents. I put in the combination by spinning the tumble lock. 2-24-45. It didnt work. 32-21-6. No luck. 204-342-654. The lock opened unsatisfyingly, requiring a little twist and tug to fully open.
I opened the pastel purple door and got inside to find the pub was full of people hiding out. Windows covered each wall and we could all observe the purple steam pressing against the building. It was a dimly lit room and the lights were flickering. There was an awful buzzing noise coming from a defective neon light from behind the bar. Despite the grim environment, the mood of the room was neutral. People spread themselves across the pub in groups, talking amongst themselves and each helping themselves to the occasional beer, spirit or wine.
I opened the fridge and helped myself to some leftovers, I took the pizza and dumplings and ate them. Both were nice, warm and made me feel hopeful.
The warmth of the food made me begin to sweat, the glaring hot sun beating down on the sandy white beach. I felt a cold loneliness and the sweat dripping down my back. The expanse was gorgeous, akin to a watercolour painting scene that contained no actors which gave it a feeling of rarity or impossibility.
This was written over a couple of days. The only goal was just to keep writing for the sake of writing while my partner was doing Nanowrimo
1 note · View note
silbinarywolf · 5 years ago
Text
You Can’t Just: Query if a controller is for Xbox or PlayStation
I'm currently on a bit of holiday leave from my full-time web dev gig and I wanted to spend some time improving the gamepad support in my newly released puzzle game, Give Up The Dupe.
I wanted to detect if a user is using a fairly modern Xbox or PlayStation controller and then make the in-game instructions be more precise for those controllers. It's a much smoother experience for the end-user if a game says "Press A to Jump" rather than saying "Press Button0 to Jump" or "Press [JUMP ACTION] to Jump".
After some initial investigation, the first thing I thought to do was call one of these functions to get the gamepad name:
Game Maker: gamepad_get_description(0)
Ebiten: ebiten.GamepadName(0)
GLFW: glfwGetJoystickName(0)
SDL2: SDL_GameControllerNameForIndex(0)
Browsers: navigator.getGamepads()[0].id
You might think that we would be able to at least fuzzily determine what controller we are using with these functions but unfortunately it's not that simple. As you can see with the examples below, you're at the mercy of your driver, engine or browser when it comes to getting a name for your gamepad.
For example, my unofficial Xbox One Controller gave me these string values:
Windows/GLFWv3.2: "Xbox 360 Controller"
Windows/GLFWv3.3: "Xbox Controller"
Windows/Game Maker: "XInput STANDARD GAMEPAD"
Windows/Chrome: "Xbox 360 Controller (XInput STANDARD GAMEPAD)"
Windows/Firefox: "xinput"
My offical PlayStation 4 controller gave me these values:
Windows/GLFW: "Wireless Controller"
Windows/Game Maker: "Sony DualShock 4"
Windows/Chrome: "Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc)"
Windows/Firefox: "054c-09cc-Wireless Controller"
Detecting the controller type via this string is probably not going to end up not being very reliable for PlayStation controller cases, especially in browsers.
Oh well. At least the controller layout of a modern Xbox and PlayStation controller is identical, so I should be able to map some reasonably good default controls. ie. "A" to Jump on a Xbox controller, "X" to Jump on a PlayStation controller.
If you use Game Maker or SDL2, you get a nice abstraction that allows you to achieve this easily, but unfortunately for us, we're using a low-level gamepad API. This means that each gamepad could have up to 32 buttons and the "A" button on an Xbox controller could be mapped between 0 and 31, the same goes for a PlayStation controllers "X" button. There is no guarantee these two controllers will use the same button slot.
In reality, you'll find that most or all Xbox controllers will map the "A" button to "Button 0" on a gamepad and all other buttons generally map to the same slots across various Xbox controllers. Unfortunately, a PlayStation controller will map the "X" button to the following button slots:
PlayStation 4 controller: "Button 1" on Windows, "Button 0" for iOS.
PlayStation 3 controller: "Button 0" or "Button 1" on Windows
PlayStation 1 or 2 controller: "Button 2" on Windows
This means that we can make a good default user-experience for players that use an Xbox controller but we cannot make any assumptions about PlayStation controllers that will "just work". It'll take some more dev. effort to provide first-class support for those.
So, assuming we are willing to do the work, what can we do? Well, thankfully there is an open-source controller mapping database that we can leverage! In-fact its the same data utilized by SDL2's higher-level gamepad API. This means we don't need to buy a bunch of different controllers and test them, other people have donated their time and effort to give us more mappings than we could have ever reasonably achieved by ourselves. Yay community!
So we have a large set of controller mappings thats regularly updated. How can we use this? Well, on most platforms there is a function that gives us the controllers GUID, which looks something like this for my respective controllers.
Xbox One Controller: 78696e70757401000000000000000000
PlayStation 4 Controller: 030000004c050000cc09000000000000
The above values were taken from glfw 3.3 for Go. I also tested calling Game Maker Studio 2's equivalent function gamepad_get_guid(id). My Xbox One Controller returned an empty string and the PlayStation 4 Controller returned the same GUID as above.
Anyway moving on. We can utilize these GUID values by searching through the aforementioned controller mapping database. In my searches, I discovered that:
The Xbox One Controller does not have a mapping that matches my GUID (and with engines like Game Maker, I just get an empty string!)
The PlayStation 4 Controller does have a mapping that matches my GUID
With these facts in mind, if we really want to support PlayStation controller mapping by default, we can load this open-source file of game controller data, use that to determine button defaults and infer the controllers shape, which would allow us to display more accurate controls on screen for PlayStation controller users.
So... at a high-level, To get both Xbox and PlayStation controllers supported, I think we want a solution that does something like:
Lookup GUID in this community-sourced gamepad mapping list. You probably want to make this file embeddable into your code so it easy to update later down the line. Infact, its worth noting that SDL2 already embeds this exact code into its high-level gamepad drivers.
Check the name associated with the GUID in the text file to determine if its a Xbox or PlayStation controller.
Fallback to using a function like glfwGetJoystickName or gamepad_get_description and checking if the name contains words like "Xbox". We will need this logic if we want Xbox controllers to be detected as we may not even get a GUID for certain controllers or on certain platforms.
If we are unable to detect the controllers shape, then fallback to standard gamepad mode and just display buttons as "[JUMP ACTION]" or "Button0".
Please keep in mind I've not implemented and shipped something like this yet, so I'm not sure if there are problems with this method that I'm not foreseeing. In anycase, attempting to do this small quality of life change ended up being a much bigger rabbithole than I expected, so I thought it was important to share what I discovered during this exploration.
1 note · View note
silbinarywolf · 6 years ago
Text
Learning Cypress.io
While researching alternatives to Jest, I stumbled upon the testing tool Cypress.io[1]. It promised to make testing pleasant at the cost of only supporting Chrome. It seemed like an even trade, so I decided to give it a go.
In my very limited experience working with other testing tools, such as Selenium and Codeception, I've found that writing tests is incredibly painful because you would ran into timing issues that would be hard to reproduce on other machines, such as waiting for DOM elements to appear within the timeout limit. Apparently, Cypress doesn't get these kind of issues because it executes directly in the browser.
So feeling inspired and hopeful about the future of web development, I decided that I would try and build a relatively simple vanilla JavaScript component like an accordion. I'd also want to try and unit test it with Cypress, even though there's no first class support yet.
But first, let's setup Webpack
It was Friday and I know that the biggest demotivator to doing frontend work in my spare-time is getting Webpack setup. With this in mind, I decided that I'd try and get a basic config setup in about an hour, just so that I could hit the ground running with coding and hacking at the problem the following day.
Needless to say, I underestimated Webpack and I'll need to continue just getting my tools setup tomorrow. Dang.
Fresh Start
I got up nice and early, feeling motivated to fix my Webpack setup and excited to experiment with Cypress. The first issue I was greeted with was a nice error message from Cypress, telling me that it had trouble parsing my code and that my Webpack configs may be incorrect. Thanks Cypress!
After resolving my transpiling issues by following the some of the Cypress documentation and looking at the Github examples, I then tried to run Cypress again.
It crashed.
I got an error with Cypress attempting to require "babel-loader". My webpack config contained nothing to do with "babel-loader" and it felt... incorrect to me that it was a requirement.
Rather than install babel-loader and keep moving forward, I wanted to understand why this was happening. So. I dug into the package where the issue was occuring and discovered that the default Webpack config that Cypress tries to utilize will call "require('babel-loader')", regardless of whether you have your own config or not.
I raised a Github issue describing how I commented out the default config and how Cypress was working without babel-loader. To resolve this problem, I proposed that babel-loader is only require()'d if there is no override. Shortly after, I raised a pull request to fix the problem. Most impressively, someone from the Cypress team responded within 3 days, accepted the PR and had it tagged in NPM as a patch version, awesome. Just awesome.
Babbys First Test
Once I had resolved the babel-loader issues, I got a very basic TypeScript test working. It checks to make sure that the number 42 is actually the number 42! (Hint: It checks out)
describe('TypeScript', () => { it('works', () => { // note TypeScript definition let x: number = 42 if (x) { expect(x).to.equal(42) } }) })
Smile
Cypress has been incredibly pleasant to use so far, it treats me like a human being. Nice readable error messages and built-in guides, telling me what I should do next. It even told me within the application to start a seperate local web server and run "cy.visit()", nice one! I love a built-in tutorial!
Whilst reading up on how this would all work and tie together, it felt a bit too heavy to me. I don't want to have to think about spawning two processes on something like TravisCI, I want things to be as simple as possible. So, because this is my spare time, I figured why not ignore the warnings on their documentation page completely and just try and experiment? What could go wrong? How much time could I really waste going down this path?
Have a break
I decided to stop there for the time being and play a bit of Overwatch with my partner. Between matches I tweaked Webpack config files and installed SASS so I could also test styling.
Node SASS no likely
I tried running my code only within Cypress.io and... no_good_emoji. node-sass started getting errors that seemed to be related to my installed Node version, it seemed to be expecting Node 8.10.0 instead of 10.6.0. I thought that perhaps NVM for Windows was being a bit janky with Cypress, so I decided to change my Node version from 10.6.0 to 8.10.0 . After hitting more errors related to the node-sass DLL being incompatible, I deleted my node_modules folder and re-ran yarn.
Argh. More errors!
Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 8.x
./src/Collapo.scss (./node_modules/css-loader??ref--4-1!./node_modules/postcss-loader/src??postcss!./node_modules/sass-loader/lib/loader.js??ref--4-3!./src/Collapo.scss) Module build failed (from ./node_modules/sass-loader/lib/loader.js): Error: A dynamic link library (DLL) initialization routine failed. \\?\D:\wamp64\www\collapo\node_modules\node-sass\vendor\win32-x64-57\binding.node @ ./src/Collapo.scss 2:14-192 @ ./cypress/integration/spec.ts
Troubleshooting
I started trying to fix the errors by loosely following a Github issue, I tried running the command:
yarn add --force node-sass
But that wasn't working. I next tried running Cypress directly, instead of through Yarn. By that I mean I executed:
node node_modules/cypress/bin/cypress open
instead of:
yarn cypress:open
I tried this because I thought that Yarn could potentially be causing an issue with how environment variables were being passed in. Unfortunately this didn't work either and so I tried to re-install node-sass at the latest version with the following command:
yarn remove node-sass yarn add --dev node-sass@latest
But I still can't get it working! I'm honestly tempted to just circumvent this issue by trying an alternative CSS preprocessor, like Stylus, as all the issues seem to be related to node-sass loading a DLL. However... since SASS is generally the agreed upon preprocessor and that my last 2 places of work were heavily invested in it, I'd rather my code be compatible with a toolset I use day-to-day.
I gotta tell ya, I'm feeling pretty frustrated by now, and really, these problems wouldn't have happened if I just did what the Cypress documentation said to do, and ran the code on a seperete local webserver.
Time for a break.
Every day, once a day, give yourself a present
I decided to go for a walk down the street and got a hot chocolate with my partner. It's a nice sunny day and stopping to have a chat and a treat is a good morale boost!
Back to business
I decided to have another crack at trying to get SASS working. So I decided to take a look at the sass-loader Github page to see if there was anything I could do to just straight-up avoid this problem rather than solve it. My intuition and past research[1] was telling me that node-sass might lead to further issues when running on a CI server, so if I can drop it, it'll probably save me future headaches as well.
After reading about the sass-loader on the Github page, I noticed that the node-sass implementation is being dropped in favour of dart-sass ("sass" package), after reading about it, I decided to give it a go.
Oh yeahhh! Now when I run Cypress, I get no node-sass related errors and I don't need to run a seperate local webserver, at least not yet! My stubbornness has paid off! I'm pleased that I found a drop-in replacement for node-sass that I can perhaps apply in my day-to-day!
module.exports = { module: { rules: [ { test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader, }, { loader: require.resolve('css-loader'), options: { // todo: Jake: 2018-11-11 // Change this loader to "2" if we use postcss below // https://github.com/webpack-contrib/css-loader#options importLoaders: 1, sourceMap: true, modules: true, localIdentName: '[name]__[local]', }, }, // todo: Jake: 2018-11-11 // Install this so -webkit-* prefixes get generated for IE/old iOS /*{ loader: require.resolve('postcss-loader'), options: { // Necessary for external CSS imports to work // https://github.com/facebookincubator/create-react-app/issues/2677 ident: 'postcss', }, },*/ { loader: require.resolve('sass-loader'), options: { implementation: require('sass'), includePaths: [ path.resolve(__dirname, 'src') ] } } ], } ] } };
CSS Modules and me
declare module '*.scss' { const content: {[className: string]: string}; export = content; }
I wanted to import SCSS files in my TypeScript files, so I added the declaration.d.ts file you see above to my project.
But... it it doesn't seem to be working. When I try and import a stylesheet with my current Webpack config, the imported object is an empty JS object. Ergh. At this point, I'm heavily considering biting the bullet and running this in a seperate server. But before I do so, I want to attempt to build the output JavaScript and CSS files without Cypress, just to determine whether this is a Cypress issue, or something else entirely.
After googling a little, I discovered a React Github issue where someone had the same issue as me! Unfortunately, it didn't relate to how I had my Webpack config setup. I'm not using React. Bummer! It did instead make me consider that maybe this issue was due to my Webpack configuration.
I looked at my loaders and did a bit of googling on each of them. Eventually, I discovered that the MiniCssExtractPlugin is not meant to be used for development builds and I tried commenting out the plugin... and...
Success! I can see the classnames in the stylesheet object I'm importing! But... we're not quite there yet. The stylesheet I'm importing isn't applying to the window. The button still looks ugly!
Dive into the DOM
I inspected the DOM with the Google Chrome DevTools and dug around. I discovered that the Cypress test script outputs in it's own sandboxed iframe and that the stylesheet was outputting inside this iframe and not in the iframe that contained the rendered button. After discovering this, I decided to try and hoist the stylesheets out of the test iframe and into the buttons iframe. It worked! Now everything is running in Cypress! Also, by the way... iframe. iframe. iframe.
The final stretch
Hooray! Things work in Cypress! All is well and good right? Nope!
My production build no longer outputs a file. Up until this point, I was trying to do everything with a single Webpack config file but it was at this point I decided to just split it into 3 files. common, dev and prod.
I struggled for a little while trying to get my configs to be as concise as I wanted because I didn't read the documentation to see how merging worked. So, I decided to look at the webpack-merge documentation. Lo' and behold! It can do smart merging for me, which allows me to prepend loaders in my dev and prod configs, this helped reduce config duplication across dev and prod.
Phew! With all this done, I'm pretty happy! I think I'll leave it here for the day!
If you want to see where I left this project at, you can see it on Github here. I've also attached my raw notes that I took as I went through the dev process, I used these to recall my process and feelings so I could write this blog post.
0 notes
silbinarywolf · 7 years ago
Text
Golang Yarns: Unused variables is an error
Hey there friend! In this post, I'm going to tell you about my experiences with unused variables giving a compile error in Golang, from my first impressions to lessons learnt after months of using the language on a hobby project.
Frustration
When I first hit a "declared and not used" error message, my initial reaction was "What flag can I use to turn this dumb 'feature' off?"
After hitting this error message a few times and feeling the frustration of this "unnecessary error", I jumped onto Google to try and understand the reasoning behind this design choice. I discovered at least one reason for this was to stop unnecessary importing of packages, which could lead to unnecessary bloat in your binary size.
When I read this, I felt partially satisifed but... I still felt annoyed by it. It just didn't seem that helpful, especially when you consider that I was simply doing hobby work, I didn't need this particular benefit.
Satisfication
After months of programming in Go for about 20-30 minutes a day on my commute to work, I found with experience that this feature also offers at least two more subtle benefits.
The first is that if you shadow a variable but forget to use it in the shadowed scope, you will get an unused variable error. If you're like me and you have trouble visualizing pseudo-code from plain English text, here's a code sample for clarity:
package main func main() { myVariable := "value" if myVariable != "" { // compile error! "myVariable" isn't used! myVariable := "shadow" } myVariable = "not value!" }
This hidden benefit probably saved me 10-20 minutes of debugging and it came from two seemingly unrelated systems complementing each other.
The second benefit you get is that your code becomes easier to read, both to yourself, others and also you in ~3 months. If your code was recently compiled sucessfully, you now have the guarantee that there are no unused variables in your program. I really love this reduction in noise and I get a warm fuzzy feeling knowing that the compiler isn't doing redundant work.
The Disciplined vs The Nanny State
So I've talked about all the benefits of this feature, but what are the tradeoffs? What are we losing with this feature?
Well... we are potentially losing effeciency if our workflow is to make a big mess and then cleanup our code before committing. I think any kind of writer can attest to the idea that editing as you go isn't the most effiecient way to pump out a book. It's a lot easier to tidy things up and improve concision when you're working with a more complete piece.
A veteran game programmer will find this feature infuriating. After all, this could simply be a warning or be ignored for "dev" builds.
In theory this is a good idea and incredibly easy to implement. However in my experience working in web development, developers tend to do the bare minimum, will ignore warnings and if the codebase got to a point where fixing errors that only happened in "production" mode was too much effort, the developer would most likely just ship "dev" builds because it's easier on them.
Conclusion
University says, Don't call your conclusion "Conclusion", but I say, nah, she'll be right.
So what's the conclusion here? I guess... in conclusion it's that we should keep in mind that if something seems like a bad idea on the surface but a group of people with decades of programming experience says its a good idea, then perhaps the value won't become apparent until you also gain some experience.
Related resources
Variable Shadowing
How to avoid annoying unused variable error - Stack Overflow
Unused Variables in Go
0 notes
silbinarywolf · 7 years ago
Text
Replace core Game Maker Studio 2 functions with your own via #macro!
EDIT, 2018-02-07: A bug has been filed here and Russell Kay has left a comment saying "This is not a bug.. this is intentional". https://bugs.yoyogames.com/view.php?id=28833
Hi there!
Today I wanted to tell you that it's currently possible to replace core Game Maker Studio 2 functions with your own scripts using the #macro functionality.
I made this discovery while experimenting on a middleware project that could benefit from being able to add hooks or override core functionality in Game Maker. (ie. you install my scripts, they just work without you needing to touch your code)
Before I dive into it, I’d like to give a quick thanks to Casey Muratori for his work on the Handmade Hero project. His tutorials taught me how to utilize C-style macros amongst many other things and this macro idea couldn't have even occurred to me without Casey's hard work.
Anyway, onto the goodies!!
So! Let’s say you want to replace all calls of keyboard_check with your own script that logs when a key is held. You would define some macros in any script like so:
#macro __keyboard_check keyboard_check #macro keyboard_check __mylibrary_keyboard_check
Then add a script to your project called __mylibrary_keyboard_check with the following:
var key = argument0 var result = __keyboard_check(key) if result { show_debug_message("MyLibrary: Holding key - "+string(key)) } return result
What's happening here you might ask?
Well, Game Maker Studio 2's preprocessor is simply replacing keyboard_check identifiers with __mylibrary_keyboard_check and it's replacing __keyboard_check with keyboard_check. (Like a text editor doing find+replace on your codebase before compiling!)
We need the __keyboard_check macro so we can utilize the real keyboard_check function within our __mylibrary_keyboard_check script. Otherwise we'll just be calling __mylibrary_keyboard_check recursively.
And really... that's it! This is something you can currently do in Game Maker! But, that being said, you probably shouldn't. If you can just find+replace variables/scripts in your project, I recommend you do that as that'll be much more readable and debuggable than abusing this.
Finally, I submitted this as a bug to YoyoGames as this seems like undefined behaviour that could lead to security holes. However, despite my recommendations being against using this functionality, I'm hoping that it's left in as it'll allow middleware scripts to "just work" without a developer needing to touch their code. Perhaps YoyoGames could add a checkbox to the IDE so that you must accept the risks of using such macros?
Example project: http://www.mediafire.com/file/oofq2uvi1fls5a8/macro_abuse.zip
(Check the console output in the Game Maker Studio 2 IDE, you'll see the show_debug_message calls are working when you press/hold a key)
Game Maker Runtime Version: 2.1.3.189
Game Maker IDE: 2.1.3.273
1 note · View note
silbinarywolf · 8 years ago
Text
Front-end Programming Language
EDIT, 2018-02-04: This post is very dated now in a lot of design decisions. This language is actively being developed at turtle speeds over here: Proposal / Scope Code
Introduction
Previously, I've talked about creating a new language specifically for doing front-end development work and I sunk a lot of hours into making a compiler in PHP that compiled a language of my own design.
While I felt it would improve my quality of life greatly as a front-end developer, I couldn't be sure others would be as excited about such a language existing and I lost a lot of motivation.
Since then, I've attempted building the compiler in C++ and then Golang. Again, losing motivation for various reasons. Despite this, I think it's a good idea to list some of the goals and show people what I had in mind for the design. It's important to note that I had zero React or virtual DOM experience before starting this project.
Goals of this front-end language
Couple HTML, CSS and Scripts as reusable modules.
Detection of browser CSS bugs at compile-time.
Integrates with backend systems smoothly and seamlessly.
Reusable Module Definitions
When I say reusable modules, I imagine something like Web Components but with a lot more power behind it. You could define a components styling, scripting, layout and properties that transform the DOM.
The pseudo-code below details how I would implement components.
def Nav { styles { nav { list-style: none; height: auto; margin: 0; padding: 0; } @media screen and (max-width: 460px) { nav { padding: 10px; } } } scripts(language = "JavaScript") { // JavaScript/CoffeyScript/whatever-else could be compiled for you here. } dom { tag nav { tag ul { children } } } // Defined under Nav, this component is only usable underneath Nav on the DOM layout. def Item { styles { li { position: relative; padding: 20px 0; } li ul { position: absolute; left: 0; top: 100%; display: none; width: 100%; margin: 0; padding: 0; list-style: none; } } properties { label = "" source = "" } dom { tag li { tag a(href = source) { // Prints the label label } if (children.length) { tag ul { // Prints the the children children } } } } } }
Once a component is defined within your front-end codebase, I'd imagine the HTML would be defined like below, not inside a 'def <Component>' type instead of separating template files and components.
dom { tag html { tag head { // Something like this for including other templates files include "Head" } tag body { Nav { Item(label = "Home", source = "http://www.google.com") Item(label = "About Us", source = "http://twitter.com") { Item(label = "How we started", source = "http://www.yahoo.com") Item(label = "What we do", source = "http://www.silbinarywolf.com.au") } } } } }
Browser CSS bug detection
Your frontend code could also have the ability to detect whether your CSS is incompatible with the browser you're targeting for whatever reason. I imagine something like:
css_validator IEInlineBlock { error = "IE6+ requires display:inline and zoom:1; to make a block element inline" div, h1, h2, h3, h4, h5, h6, p, form, header, footer, section { display: inline-block; !display: inline; !zoom: 1; } } css_validator BackgroundPosXY { error = "background-position-x and background-position-y do not exist on Firefox" * { background-position-x; background-position-y; } }
Integrates with backend systems seamlessly
By this, I mean that you can pass data from say PHP/NodeJS/Ruby to a component and it would do it's best to handle that data appropriately. Imagine in my PHP code I've done the following:
<?php $myArray = array( array( 'label' => 'Home', 'source' => 'http://www.google.com', ), array( 'label' => 'About Us', 'source' => 'http://twitter.com', 'items' => array( array( 'label' => 'How we started', 'source' => 'http://www.yahoo.com' ), array( 'label' => 'What we do', 'source' => 'http://www.silbinarywolf.com.au' ), ) ), )
Now imagine the same template file code shown above, but it's using:
dom { tag html { tag head { } tag body { if $myArray { Nav { loop $myArray as $navItem { Item($navItem) { if $navItem.items { loop $navItems.items as $subNavItem { Item(label = $subNavItem.label, source = $subNavItem.source) } } } } } } } } }
Now, a caveat with this approach to allowing backend data into the mix, is that the 'Item' component can no longer determine whether it has children or not with 'children.length' as that information is decided at the backend runtime
0 notes
silbinarywolf · 10 years ago
Text
4 Things I wish I knew about CSS a year ago
1. Use tables for centering content vertically
Tables used to be the de-facto way to get your layout going in the olden days and they're for vertically centering content and having columns grow naturally in height without JavaScript.
For IE8+ users
.two-column-row { display: table; table-layout: fixed; width: 100%; } .two-column-col { display: table-cell; width: 50%; vertical-align: middle; }
<div class="two-column-row"> <div class="two-column-col"> My vertically centered text here </div> <div class="two-column-col"> My vertically centered text here </div> </div>
For IE7 and below users
Unfortunately, IE7 and below do not support 'display: table;' or 'display: table-cell;'. The non-JavaScript solution to this is to simply use a <table> tag and put the 'role=presentation' attribute on it. This ensures that screen-readers know that the table isn't representing data.
A real case for needing to support IE6 or IE7 is that government systems run on very outdated operating systems and need their website to work with those systems.
.two-column-row { table-layout: fixed; width: 100%; } .two-column-col { width: 50%; vertical-align: middle; }
<table role="presentation" class="two-column-row"> <tbody><tr> <td class="two-column-col"> My vertically centered text here </td> <td class="two-column-col"> My vertically centered text here </td> </tr></tbody> </table>
2. Justify Grid
What would you say if there was a grid system that supported old browsers (IE6+) and just deals with margins for you?
Well there is! It's called the Justify Grid (Github).
It works by setting the font size to 0 on the row element and setting the display to 'inline-block' on all child elements. This causes the browser to not render spaces (as it normally would for text) and keep the elements packed together. Then it justifies the elements to spread the elements evenly.
I should also note that this is great for centering a group of elements with 'text-align: center;'
.justifygrid { text-align: justify !important; text-justify: distribute-all-lines; /* For IE */ font-size: 0 !important; } .justifygrid:after { content: ""; display: inline-block; width: 100%; } .justifygrid > * { vertical-align: top; display: inline-block; width: 100%; text-align: left; font-size: medium; } .lt-ie8 .justifygrid { zoom: 1; } .lt-ie8 .justifygrid > * { display: inline; } .span-1 { width: 5.298013%; } .span-2 { width: 13.907285%; } .span-3 { width: 22.516556%; } .span-4 { width: 31.125828%; } .span-5 { width: 39.735099%; } .span-6 { width: 48.344371%; } .span-7 { width: 56.953642%; } .span-8 { width: 65.562914%; } .span-9 { width: 74.172185%; } .span-10 { width: 82.781457%; } .span-11 { width: 91.390728%; }
I use this thing a lot because it allows me to not have to think about margins at all.
Unfortunately it comes with caveats, the big one being that when you insert items into your page via AJAX, all the items lose their spacing.
To remedy the, you need to insert a text node in between each of the items, like below:
$(".justifygrid").children().each(function(index) { this.parentNode.insertBefore(document.createTextNode(" "), this); });
The same 'no spacing' issue can occur if you minify your markup and/or remove ALL spacing between the elements as well, so be wary of that.
3. Applying padding to a percentage width column without box-sizing: border-box (To support older browsers like IE6/IE7)
This one is pretty simple once you know how to do it, but it's simply:
.my-row { width: 100%; } .my-column { float: left; width: 50%; } .my-column-inner { /* Do not apply a width here or reset to width: auto; */ padding: 15px; }
<div class="my-row"> <div class="my-column"> <div class="my-column-inner"> <!-- Content in here --> </div> </div> <div class="my-column"> <div class="my-column-inner"> <!-- Content in here --> </div> </div> </div>
4. The ‘color’ property is inherited:
This might be very obvious for those who I have a good grasp of how CSS works, but I feel it’s one of those things you won’t realize you can do until you’ve seen someone else do it or been told you can do it.
So why is this useful? Well, a great use case is when you want the font colour to go from say, black to white on an entire element block.
body { color: #000; } .font-light { color: #fff; }
Believe it or not, before I discovered this, I used have to write color overrides like so:
body h1, body h2, body h3, body h4, body h5, body h6, body p { color: #000; } .font-light h1, .font-light h2, .font-light h3, .font-light h4, .font-light h5, .font-light h6, .font-light p { color: #fff; }
As you can clearly see, way more CSS for way less maintainability. Try to avoid applying colours to elements directly if you want to take advantage of this.
3 notes · View notes
silbinarywolf · 10 years ago
Text
Loading PNG files with stb_image and SDL2
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" SDL_Surface *LoadImage(char* filename) { // Read data int32 width, height, bytesPerPixel; void* data = stbi_load(filename, &width, &height, &bytesPerPixel, 0); // Calculate pitch int pitch; pitch = width * bytesPerPixel; pitch = (pitch + 3) & ~3; // Setup relevance bitmask int32 Rmask, Gmask, Bmask, Amask; #if SDL_BYTEORDER == SDL_LIL_ENDIAN Rmask = 0x000000FF; Gmask = 0x0000FF00; Bmask = 0x00FF0000; Amask = (bytesPerPixel == 4) ? 0xFF000000 : 0; #else int s = (bytesPerPixel == 4) ? 0 : 8; Rmask = 0xFF000000 >> s; Gmask = 0x00FF0000 >> s; Bmask = 0x0000FF00 >> s; Amask = 0x000000FF >> s; #endif SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(data, width, height, bytesPerPixel*8, pitch, Rmask, Gmask, Bmask, Amask); if (!surface) { // NOTE: Should free stbi_load 'data' variable here return NULL; } return surface; }
0 notes