Refactoring gets really bad reviews, but from where I'm sitting as a hobby programmer in relative ignorance it seems like it should be easier, because you could potentially reuse a lot of code. Can someone break it down for me?
I'm thinking of a situation where the code is ugly but still legible here. I completely understand that actual reverse engineering is harder than coding on a blank slate.
Even through it has some flaws, e.g. it's not fully memory safe (there are some programming languages that are even safer, like Ada)?
Anyone else doing it? I am doing the first 3 days today to catch up. Just finished the first one!
Doing it with Rust to get more familiar with it. If anyone wants to talk about specific puzzles, we can do it using spoiler tags!
I created my "cafe" in IRC (#codecrafters at libera.chat). This is a place for people who perceive programming as creativity, art, handmade, crafting. Here we are not tied to C or other languages. If you are a Rust programmer and do not want to rewrite the whole world in Rust, you are welcome. If you are a JavaScript programmer and do not think that you need to learn TypeScript instead of JavaScript (because it is unsafe) and that everything should be written in Angular and React, you are welcome. You are tired of writing SaaS garbage, you are welcome. Whether you are a CSS artist, or just a creator, you are welcome. No idols, fanaticism, or heroes.
A cozy place where you can share a common vision and feeling with like-minded people. The only rule is to be human and respectful.
Official description: ~ A cozy place in the jungle of the techno world for all programmers who like to create high-quality and effective code from scratch with their own hands. Hobbyists, professionals, beginners, and just curious about how things really work. Handmade, free and open-source software written with a love for engineering and deep knowledge is code crafting. ~
Welcome to our campfire: irc://irc.libera.chat/#codecrafters.
Let's say you decide to learn programming. You have two options. Either use the education system (college or courses) or become self-taught. In the first case, you will learn the programming languages that are imposed on you. The education system (universities, colleges, courses) uses the "modern" development stack. Because what matters to them is what can later bring income to companies and you in life, and taxes to the state. They are part of the system and that's how it works. Or because they want to get certificates from industry giants and use everything in their implementation - from development tools to ideology. Only a very few colleges and courses specializing in a very narrow field, such as embedded devices, can teach you the C language.
If you choose to learn programming on your own, the first thing you will do is go to the Internet to determine where to start and what you need to learn today. Naturally, you will find there a lot of articles and posts on the topic of "what programming language to learn in X year". And they will contain a detailed or not very detailed comparison of “modern” languages. But you are unlikely to find the C language among them. Moreover, almost all of these languages will have the intention of being “C replacements”. Naturally, you will choose a new, powerful, and promising language that will replace the “dying C”, while you “look to the future”. You will never find phrases like “Rust is a replacement for Zig” or vice versa, they will all be “replacements for C”. And by doing this they are trying to hide the C language. We have seen why the C programming language is hidden.
But suddenly one wonderful day you came across a post with the words “give C language a try”, or, if you are over 40, you remembered where you started learning programming as a child before you started writing all this “SaaS garbage”. And you thought “well, okay, what if there is something, here is nothing to lose anyway”. And you started learning C, simultaneously integrating into the C community. And then you discover, to your surprise, that the C language is simple and effective, applicable everywhere, and continues to develop. And the community is kind, not pompous, without hype, and buzzing with interesting projects. You realized that the C language is not dying and is not going to die, as the "gurus" on youtube taught you and representatives of the "modern" language communities argued with foam at the mouth. And that it is unlikely that C will be able to replace anything in the near future. It's as if you have found "your home" again, something you have been looking for a long time, but could not express in words. You have returned to the roots. And this is why the C language is gold.
Look for your "gold", never give up. When you find it, you will know for sure that this is it. Thanks for reading!
Why am I writing this post? Not because I hope for something or believe in change. These are just words. I could write this at the end, but then you would be looking for answers for me while reading, and I don’t need them. They won’t change anything. So here it is. I don’t claim to be a software development guru or a C language expert. I’m just a simple developer.
-
Why are we looking for new technologies? Why do we want to be part of a community that is buzzing with new projects? Why do we think that this new programming language will definitely help us create something amazing and truly great and, of course, will make us rich and provide us with a comfortable old age?
-
Why are we offered so many courses in so many programming languages and frameworks? Why do we teach what is required for companies that make money from us?
-
Why are there a lot of conferences on banal simple things, such as *** framework or ### technology (so as not to offend anyone), and there, with a smart look, newly minted gurus tell us how important it is to be able to transfer the value to the client and how to use certain templates?
-
Why do computers become more and more powerful, but programs continue to lag?
-
Why, when applying for a job, do we look for a vacancy based on knowledge of a programming language, but find it only based on knowledge of certain frameworks? Is it really difficult for a professional programmer to learn a framework in a week?
-
Why do we go into software development with the enthusiasm to create something great, but end up in a situation where we are developing some other catalog or some other digital yo-yo to make money?
Reason: because we want our passion for programming, our interest, to also bring us income.
Result: we do not earn this money for ourselves, but for companies whose main goal is to quickly receive income from the software they sell.
I look at how programming has changed over the course of 25 years, what they teach at universities, and where they start. And I came to the conclusion that on a large scale, it was all for the benefit of giant companies or the government.
We must protect the “intimate” knowledge of the foundations and water the roots ourselves. Because they don’t realize, they don’t see that if the roots are not watered, the branches on which they sit will dry out. Therefore, who, if not us?!
There are a lot of questions and intentions to move into gamedev from developers who are burnt out at their jobs. And that’s okay. From my own experience, I have a couple of pieces of advice that are not very professional.
1. It won’t save you from everything you’re so tired of.
Firstly, game development, like other areas, is full of its own nuances and pitfalls. And given that a person gets used to everything, you will soon find yourself in the same position. It’s better to look at game development as a hobby, a distraction from your main job. Moreover, for the first few years you will still not be able to earn enough to support yourself and your family.
2. There are no universal tools.
The main question in any field of programming today is which framework and programming language to learn. Here everyone will choose their own - what they can master. But it’s worth noting that in game development when switching, for example, from web development, you need to understand that you won’t be able to use React or even JavaScript if you want to become a real pro. You have to be willing to study hard. These are low-level languages - C, C++, and the basics of mathematics and physics, and possibly machine learning. It won’t be easy, you just have to keep going. Take a break and study further. There is no need to strive to immediately choose the top and most complex tools; the main thing is to start somewhere.
3. This is a market with tough players.
If you think that you can create a game in a couple of months and immediately start making money, then this is not so. Of course, you can try, but the network is already full of low-grade content, and sometimes you just wonder about the mental health of the “creator”. I think it’s better to create one project, but ideal, adequate and interesting.
4. Hype is temporary, and you only live once.
Lots of technologies, engines, etc. surrounded by a lot of hype. This is not bad for the creators of these things, but if you run after the clouds, you will never get anything done. Let your achievements be modest, but they will be yours. This will save you from burnout at your main job, otherwise there will only be dissatisfaction with yourself.
Add your own…
cross-posted from: https://programming.dev/post/21696778
- What's new in .NET 9
- What's new in ASP.NET Core 9.0
- What's New in EF Core 9
- What's new in Windows Forms for .NET 9
- What's new in WPF for .NET 9
- What's new in .NET MAUI for .NET 9
- Latest stable channel release notes for the Windows App SDK
- What's new in C# 13
- What's new in F# 9
- Visual Studio 2022 version 17.12 announcement, release notes
if social media groups hardly attract any traffic to our blogs what tools do you use to get traffic to your blog? Come and talk to me in this room and let's find out how to do it. https://chat-to.dev/chat?q=more_traffic_for_web
cross-posted from: https://lemm.ee/post/46067136
I'm designing a webapp that is supposed to be an AR environment on your phone, to be viewed with something like Google Cardboard, but I am having an issue that the segmentPointer object that is meant to appear when clicking on an object is not.
I've checked the geometry displays correctly in a sandbox, and when I change it to a 3d object rather than shapeGeometry it does display, but I cannot figure out why it is not displaying how I want it to.
The project is at https://voxelverse.jackgreenearth.org, and the code is quite long, but it is here to read in its totality below as it might need the whole context to discover the error. I've tried myself looking through the code, and I've tried searching the web and asking LLMs, but I couldn't figure it out, so please help me, fellow humans.
Tap for code
"use strict"; import \* as THREE from 'three'; import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js'; \ const loader = new GLTFLoader(); const textureLoader = new THREE.TextureLoader(); const manager = THREE.DefaultLoadingManager; \ // Basic functions \ function ls(id) { return(localStorage.getItem(id)); }; \ function setLs(id, val) { localStorage.setItem(id, val); }; \ function byId(id) { return(document.getElementById(id)); }; \ function bySel(sel) { return(document.querySelector(sel)); }; \ function byClass(id) { return(document.getElementsByClassName(id)); }; \ function toTitleCase(str) { return str.replace( /\w\S\*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); } ); }; \ function randInt(max) { return Math.floor(Math.random() \* (max)); }; \ function getRandomFloat(min, max, decimals) { return(parseFloat((Math.random() \* (max - min) + min).toFixed(decimals))); }; \ function confine(value, min, max) { if(value < min) { return(min); } else if(value > max) { return(max); } else { return(value); }; }; \ function wrap(value, min, max) { const range = max - min; \ if(value < min) { return(wrap(value + range, min, max)); } else if(value > max) { return(wrap(value - range, min, max)); } else { return(value); }; }; \ function removeFromArray(array, forDeletion) { return(array.filter(item => !forDeletion.includes(item))); }; \ function radToDeg(radians) { return radians \* (180 / PI); } \ function range(start, stop, step = 1) { if (stop === undefined) { stop = start; start = 0 } return Array.from({ length: (stop - start) / step }, (\_, i) => start + (i \* step)); } \ function between(variable, min, max, inclusive='min') { switch(inclusive) { case 'none': return((variable > min) && (variable < max)); break; case 'both': return((variable >= min) && (variable <= max)); break; case 'min': return((variable >= min) && (variable < max)); break; case 'max': return((variable > min) && (variable <= max)); break; } } \ function download(data, filename, type) { var file = new Blob(\[data], {type: type}); if (window\.navigator.msSaveOrOpenBlob) // IE10+ window\.navigator.msSaveOrOpenBlob(file, filename); else { // Others var a = document.createElement("a"), url = URL.createObjectURL(file); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(function() { document.body.removeChild(a); window\.URL.revokeObjectURL(url); }, 0); }; }; \ function log(text) { console.log(text); }; \ function distance2d(x1, y1, x2, y2) { return(Math.sqrt( (Math.abs(x1 - x2) \*\* 2) + (Math.abs(y1 - y2) \*\* 2) )); }; \ function distance3d(p1 = new THREE.Vector3(0, 0, 0), p2 = new THREE.Vector3(0, 0, 0)) { return(Math.sqrt((distance2d(p1.x, p1.y, p2.x, p2.y) \*\* 2) + (Math.abs(p1.z - p2.z) \*\* 2))); }; \ let totalElementsToLoad = 0; let numberOfElementsLoaded = 0; \ function onAllElementsLoaded() { \ } \ function load(path, type, functionOnLoad) { totalElementsToLoad += 1; \ if(type == 'html') { fetch(path) .then(response => response.text()) .then(html => { let doc = new DOMParser().parseFromString(html, "text/html"); \ functionOnLoad(doc); \ // If all elements to load have been loaded, execute the relevant function numberOfElementsLoaded += 1; if(numberOfElementsLoaded == totalElementsToLoad) { onAllElementsLoaded(); } }) .catch(error => { console.error(error); }); } else if(type == 'json') { fetch(path) .then(response => response.json()) // parse the response as JSON .then(json => { functionOnLoad(json); \ // If all elements to load have been loaded, execute the relevant function numberOfElementsLoaded += 1; if(numberOfElementsLoaded == totalElementsToLoad) { onAllElementsLoaded(); } }) .catch(error => { console.error(error); }); } } \ // Setup \ const PI = 3.1415926535897932384626433832795028841971; \ // Objects \ let orientation = { 'absolute': false, 'alpha': 0, 'beta': 0, 'gamma': 0 } \ // vars const fps = 60; \ let keysDown = \[]; let pointerPosition = {'x': 0, 'y': 0, 'positions': \[{'clientX': 0, 'clientY': 0}], 'type': 'mouse'}; \ // Camera let cameraRotation = new THREE.Euler(0, 0, 0, 'YXZ'); let cameraTargetRotation = {'x': 0, 'y': 0, 'z': 0}; const cameraRotationSensitivity = 0.002; \ // Other variables let logicInterval; \ // Load default settings let defaultSettings; \ load("/assets/json/default-settings.json", 'json', function(defset) { defaultSettings = defset; \ // Create custom settings if(!Object.keys(localStorage).includes('settings')) { setLs('settings', JSON.stringify({})); }; \ onSettingsLoad(); }); \ function settingURL(url, addValue=true) { return('children/' + url.split('/').join('/children/') + (addValue ? '/value' : '')); } \ function customiseSetting(url, value) { url = settingURL(url).split('/'); \ let newSettings; \ function recursiveSet(object, list, index, setTo) { // If the current component is the last one, assign the value if(index == list.length - 1) { object\[list\[index]] = setTo; return(object); } else { // Check if it already contains the value if(object.hasOwnProperty(list\[index])) { object\[list\[index]] = recursiveSet(object\[list\[index]], list, index + 1, setTo); } else { object\[list\[index]] = recursiveSet({}, list, index + 1, setTo); } return(object); } }; \ newSettings = recursiveSet(JSON.parse(ls('settings')), url, 0, value); \ setLs('settings', JSON.stringify(newSettings)); } \ function getSetting(url, addValue) { url = settingURL(url, addValue).split('/'); \ function recursiveGet(object, list, index) { // If the current component is the last one, return the value if (index == list.length - 1) { return object\[list\[index]]; } else { // Check if it contains the value if (object.hasOwnProperty(list\[index])) { return recursiveGet(object\[list\[index]], list, index + 1); } else { return null; // No such setting } } } \ // Try to find it in local settings first, otherwise get it from defaultSettings const localGet = recursiveGet(JSON.parse(ls('settings')), url, 0); if(localGet == null) { return(recursiveGet(defaultSettings, url, 0)); } else { return(localGet); } } \ // First, lets define some functions // Rendering functions \ // Thanks, https\://discourse.threejs.org/t/roundedrectangle-squircle/28645! function roundRectangleGeometry(w, h, r, s) { // width, height, radius corner, smoothness // helper const's const wi = w / 2 - r; // inner width const hi = h / 2 - r; // inner height const w2 = w / 2; // half width const h2 = h / 2; // half height const ul = r / w; // u left const ur = ( w - r ) / w; // u right const vl = r / h; // v low const vh = ( h - r ) / h; // v high let positions = \[ -wi, -h2, 0, wi, -h2, 0, wi, h2, 0, -wi, -h2, 0, wi, h2, 0, -wi, h2, 0, -w2, -hi, 0, -wi, -hi, 0, -wi, hi, 0, -w2, -hi, 0, -wi, hi, 0, -w2, hi, 0, wi, -hi, 0, w2, -hi, 0, w2, hi, 0, wi, -hi, 0, w2, hi, 0, wi, hi, 0 ]; let uvs = \[ ul, 0, ur, 0, ur, 1, ul, 0, ur, 1, ul, 1, 0, vl, ul, vl, ul, vh, 0, vl, ul, vh, 0, vh, ur, vl, 1, vl, 1, vh, ur, vl, 1, vh, ur, vh ]; let phia = 0; let phib, xc, yc, uc, vc, cosa, sina, cosb, sinb; for (let i = 0; i < s \* 4; i ++) { phib = Math.PI \* 2 \* ( i + 1 ) / ( 4 \* s ); cosa = Math.cos( phia ); sina = Math.sin( phia ); cosb = Math.cos( phib ); sinb = Math.sin( phib ); xc = i < s || i >= 3 \* s ? wi : - wi; yc = i < 2 \* s ? hi : -hi; positions.push( xc, yc, 0, xc + r \* cosa, yc + r \* sina, 0, xc + r \* cosb, yc + r \* sinb, 0 ); uc = i < s || i >= 3 \* s ? ur : ul; vc = i < 2 \* s ? vh : vl; uvs.push( uc, vc, uc + ul \* cosa, vc + vl \* sina, uc + ul \* cosb, vc + vl \* sinb ); phia = phib; } const geometry = new THREE.BufferGeometry( ); geometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) ); geometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( uvs ), 2 ) ); return geometry; } \ // Render function render() { requestAnimationFrame(render); leftRenderer.render(scene, leftCamera); rightRenderer.render(scene, rightCamera); \ framesSoFar++; }; \ // Functions function setCameraRotation() { // Calculate drag cameraRotation.x = Number((cameraRotation.x + ((cameraTargetRotation.x - cameraRotation.x) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5)); cameraRotation.y = Number((cameraRotation.y + ((cameraTargetRotation.y - cameraRotation.y) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5)); cameraRotation.z = Number((cameraRotation.z + ((cameraTargetRotation.z - cameraRotation.z) / getSetting('Input/Mouse/Camera Rotation Drag'))).toFixed(5)); // Update cameras for(let camera of \[leftCamera, rightCamera]) { camera.rotation.set(cameraRotation.x, cameraRotation.y, cameraRotation.z, 'YXZ'); } \ const eyeGap = getSetting('Quick Settings/Eye Gap'); \ // Set camera positions leftCamera.position.x = -1 \* eyeGap \* Math.sin(cameraRotation.y); leftCamera.position.z = -1 \* eyeGap \* Math.cos(cameraRotation.y); rightCamera.position.x = eyeGap \* Math.sin(cameraRotation.y); rightCamera.position.z = eyeGap \* Math.cos(cameraRotation.y); \ byId('camera-target-rot-x').innerHTML = cameraTargetRotation.x.toFixed(2); byId('camera-target-rot-y').innerHTML = cameraTargetRotation.y.toFixed(2); byId('camera-target-rot-z').innerHTML = cameraTargetRotation.z.toFixed(2); byId('camera-rot-x').innerHTML = cameraRotation.x.toFixed(2); byId('camera-rot-y').innerHTML = cameraRotation.y.toFixed(2); byId('camera-rot-z').innerHTML = cameraRotation.z.toFixed(2); \ byId('camera-left-rot-x').innerHTML = leftCamera.rotation.x.toFixed(2); byId('camera-left-rot-y').innerHTML = leftCamera.rotation.y.toFixed(2); byId('camera-left-rot-z').innerHTML = leftCamera.rotation.z.toFixed(2); } \ function takeScreenshot() { downloadCanvasImage(document.getElementById('game-canvas'), gameName + ' screenshot'); sendAlert('Screenshot Taken!', 'tick'); }; \ function takePanorama() { const canvas = document.getElementById('game-canvas'); const height = canvas.height; const width = canvas.width \* (360 / (camera.fov \* camera.aspect)); let newCanvas = document.createElement('canvas'); newCanvas.height = height; newCanvas.width = width; newCanvas.style.display = 'none'; let context = newCanvas.getContext("2d"); document.body.appendChild(newCanvas); for(let x = 0; x < width; x++) { // Rotate cameraRotation.y += ((2 \* PI) / width); let calculatedRotation = rotationToAbsolute(playerPosition, cameraRotation); camera.rotation.set(calculatedRotation.x, calculatedRotation.y, calculatedRotation.z, 'YXZ'); renderer.render(scene, camera); const gl = renderer.getContext(); // Get canvas data const pixelData = new Uint8ClampedArray(1 \* height \* 4); const reversedPixelData = new Uint8ClampedArray(1 \* height \* 4); gl.readPixels((canvas.width / 2), 0, 1, height, gl.RGBA, gl.UNSIGNED\_BYTE, pixelData); for (let i = 0; i < height; i++) { for (let j = 0; j < 4; j++) { reversedPixelData\[i\*4 + j] = pixelData\[(height - i - 1)\*4 + j]; }; }; const imageData = new ImageData(reversedPixelData, 1, height); context.putImageData(imageData, x, 0); }; downloadCanvasImage(newCanvas, gameName + ' panorama'); newCanvas.remove(); sendAlert('Panoramic screenshot taken!', 'tick'); }; \ function setRotation(object, rotation) { object.rotation.set(rotation.x, rotation.y, rotation.z); }; \ function downloadCanvasImage(canvas, name) { let canvasImage = canvas.toDataURL('image/png'); // this can be used to download any image from webpage to local disk let xhr = new XMLHttpRequest(); xhr.responseType = 'blob'; xhr.onload = function () { let a = document.createElement('a'); a.href = window\.URL.createObjectURL(xhr.response); a.download = name; a.style.display = 'none'; document.body.appendChild(a); a.click(); a.remove(); }; xhr.open('GET', canvasImage); // This is to download the canvas image xhr.send(); }; \ function xyToRealPosRot(x, y, distance) { let realX, realY, realZ, rotX, rotY, rotZ; \ // Position is an object {x: x, y: y} x determines which face it will be on horizontally, and y determines if it will be on the top or the bottom // Beyond 400, x position wraps x = wrap(x, 0, 400); log('x before: ' + x) const horizontalFace = (x / 100) % 4; //rotY = (x / 400) \* (1) // horizontalFace); \ // The top of the screen is y 100, the bottom is y -100, and the horizontals are between -50 and 50 realY = confine(y, -100, 100); \ // Calculate real position const unit = getSetting('Display/UI/Distance') / 50; \ let forward = getSetting('Display/UI/Distance'); \ const bevel = getSetting('Display/UI/Bevel'); \ rotX = 0; \ // If it is horizontal... if(between(y, -50 + bevel, 50 - bevel)) { realY = y; rotX = 0; } else if(y < -50 - bevel) { // If it is on the lower face realY = -50; forward = (y + 100) \* unit; rotX = -(PI / 2); } else if(y >= 50 + bevel) { // If it is on the upper face realY = 50; forward = (y - 100) \* unit; //side = unit \* (((x - 50) % 100) + 50); rotX = (PI / 2); } else if(between(y, -50 - bevel, -50 + bevel)) { // If it is on the lower bevel realY = -50 - ((y + 50) / 2); rotX = (PI / 4); } else if(between(y, 50 - bevel, 50 + bevel)) { // If it is on the upper bevel realY = 50 + ((y - 50) / 2) ; rotX = -(PI / 4); } \ realY = realY \* unit; \ let flip = false; \ /\*if( (horizontalFace >= 0 && horizontalFace < 0.5) || (horizontalFace >= 1.5 && horizontalFace < 2.5) || (horizontalFace >= 3.5 && horizontalFace < 4) ) { flip = true; }\*/ \ let angle = (x / 400) \* (PI \* 2); realX = Math.sin(angle) \* forward; realZ = Math.cos(angle) \* forward; rotY = angle; log('rot y: ' + rotY) \ log({ 'x': realX, 'y': realY, 'forward': forward, }) \ // Take distance into account realX \*= distance; realY \*= distance; realZ \*= distance; \ return({ 'position': new THREE.Vector3(realX, realY, realZ), 'rotation': new THREE.Euler(rotX, rotY, rotZ, 'YXZ'), 'flip': flip }); } \ function addWidget({ name = '', position = {'x': 0, 'y': 0}, rotation = {'x': 0, 'y': 0, 'z': 0}, distance = 1, size = {'x': 10, 'y': 10}, radius = 3, shape = 'rRect', background = '#000000', opacity, textStyle = { 'align': 'center', 'weight': 0, // Range is 0 to 10 'font': 'DINRoundPro,arial,sans-serif', 'color': '#b0b0b0', 'vertical-align': 'center', 'font-size': 1 // Uses the same sizing system as the rest of the UI, so one unit of text is also one unit of object }, textContent = '', onclick = function() {}, onlongpress = function() {}, onhover = function() {}, onhoverexit = function() {}, ontruehover = function() {} }) { const realPosRot = xyToRealPosRot(position.x, position.y, distance); log(realPosRot) const realPos = realPosRot.position; let realRot = realPosRot.rotation; \ realRot.x += rotation.x; realRot.y += rotation.y; realRot.z = rotation.z; \ // Calculate real size const unit = getSetting('Display/UI/Distance') / 100; \ let width = unit \* size.x; let height = unit \* size.y; radius \*= unit; const scale = getSetting('Display/UI/Scale/General'); width \*= scale; height \*= scale; radius \*= scale; \ // Set mesh geometry let geometry; switch(shape) { case 'rRect': geometry = roundRectangleGeometry(width, height, radius, 10); break; case 'rect': geometry = new THREE.PlaneGeometry(width, height); break; case 'circle': geometry = new THREE.CircleGeometry((width + height) / 2, 32); break; } let material; \ if(opacity == undefined) { opacity = 1; } \ if(textContent == '') { if(background\[0] == '/') { loadTexture(background, function(texture) { material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide, opacity: opacity, transparent: true }); onTextureLoad(material); }) } else { material = new THREE.MeshBasicMaterial({ color: background, side: THREE.DoubleSide, opacity: opacity, transparent: true }); onTextureLoad(material); } } else { function prepareText(canvas) { // Proceed to prepare the canvas with the text ctx.font = \`${textStyle\["font-size"]}em ${textStyle\["font"]}\`; ctx.textAlign = textStyle\["align"]; ctx.fillStyle = textStyle\["color"]; ctx.fillText(textContent, 0, 0); // Compose the text onto the background const composedTexture = new THREE.CanvasTexture(canvas); \ // Generate the material material = new THREE.MeshBasicMaterial({ map: composedTexture, side: THREE.DoubleSide, transparent: true, alphaTest: 0.5 }); \ onTextureLoad(material); } \ // Initialize tmpcanvas only when needed const tmpcanvas = document.createElement('canvas'); tmpcanvas.width = width; tmpcanvas.height = height; const ctx = tmpcanvas.getContext('2d'); \ \ // Fill the background first if (background\[0] == '/') { loadTexture(background, function(texture) { ctx.fillStyle = texture; ctx.fillRect(0, 0, width, height); \ prepareText(tmpcanvas); }) } else { ctx.fillStyle = background; ctx.fillRect(0, 0, width, height); \ prepareText(tmpcanvas); } } function onTextureLoad(material) { // Create a mesh with the geometry and the material let mesh = new THREE.Mesh(geometry, material); \ mesh.name = name; \ mesh.position.set(realPos.x, realPos.y, realPos.z ); mesh.rotation.set(realRot.x, realRot.y, realRot.z); \ if(realPosRot.flip) { mesh.scale.x = -1; } \ mesh.onclick = onclick; mesh.onlongpress = onlongpress; mesh.onhoverexit = onhoverexit; mesh.ontruehover = ontruehover; mesh.onchover = onhover; \ scene.add(mesh); }; } \ function transitionWidget(name, property, newProperty, time, condition) { if(condition != null) { } } \ // three.js Scene setup const scene = new THREE.Scene(); \ // three functions \ function loadTexture(path, onload) { textureLoader.load(path, function (texture) { onload(texture); }, undefined, function (error) { console.error(error); }); }; \ // Define objects to make them global, they will mostly be only added to the scene when settings are loaded let sun; let wallpaper; let cameraStream; let pointer; \ let pointerMaterial = new THREE.MeshBasicMaterial({ color: "hsl(0, 100%, 50%)", side: THREE.DoubleSide }); \ let segmentShape = new THREE.Shape(); let segmentGeometry = new THREE.ShapeGeometry(segmentShape); let segmentPointer = new THREE.Mesh(segmentGeometry, pointerMaterial); segmentPointer.name = 'segmentPointer'; \ function setSegmentPointer(angle = 0, radius = 0.1, rotation = new THREE.Euler(0, 0, 0), clockwise=true) { let oldGeometry = segmentPointer.geometry; \ let segmentShape = new THREE.Shape(); segmentShape.moveTo(0, 0); segmentShape.arc(0, 0, radius, 0, angle, clockwise); segmentShape.lineTo(0, 0); \ let extrudeSettings = { steps: 1, depth: 0.1, bevelEnabled: false }; \ let segmentGeometry = new THREE.ExtrudeGeometry(segmentShape, extrudeSettings); segmentPointer.geometry = segmentGeometry; \ oldGeometry.dispose(); \ segmentPointer.rotation.set(rotation); } \ // Camera stuff let cameraViewDistance; \ // Setup cameras let leftCamera; let rightCamera; let leftRenderer; let rightRenderer; \ function setRendererSize() { for(let renderer of \[leftRenderer, rightRenderer]) { let canvas = renderer.domElement; renderer.setSize( canvas.offsetWidth \* getSetting('Display/Anti-alias'), canvas.offsetHeight \* getSetting('Display/Anti-alias'), false ); } } \ function updateCameraAspectRatio() {-0.2 for(let camera of \[leftCamera, rightCamera]) { let canvas = leftRenderer.domElement; camera.aspect = canvas.offsetWidth / canvas.offsetHeight; camera.updateProjectionMatrix(); } } \ // temp function startAssistant() { log('assisstant') } \ // When settings are loaded, start settings stuff up function onSettingsLoad() { // Add sun sun = new THREE.PointLight(0xffffff, getSetting('Quick Settings/Brightness')); scene.add(sun); \ // Pointers pointer = new THREE.Mesh(new THREE.IcosahedronGeometry(getSetting('Display/UI/Pointer/Size'), 1), pointerMaterial); pointer.name = 'pointer'; scene.add(pointer); \ pointerMaterial = new THREE.MeshBasicMaterial( {color: "hsl(" + (getSetting('Quick Settings/Theme Hue') + getSetting('Display/UI/Pointer/Hue Shift')) + ", 100%, 50%)"} ); pointer.material = pointerMaterial; segmentPointer.material = pointerMaterial; \ // Add wallpaper let wallpaperURL; if(getSetting('Display/UI/Wallpaper/Use Direct Link') == true) { wallpaperURL = getSetting('Display/UI/Wallpaper/Direct Link'); } else { wallpaperURL = getSetting('Display/UI/Wallpaper/Wallpapers Folder') + '/' + getSetting('Display/UI/Wallpaper/Wallpaper'); } \ loadTexture(wallpaperURL, function(texture) { let material = new THREE.MeshStandardMaterial({ map: texture, side: THREE.BackSide, transparent: true, opacity: getSetting('Display/UI/Wallpaper/Opacity') }); \ wallpaper = new THREE.Mesh( new THREE.IcosahedronGeometry( getSetting('Display/UI/Distance') \* getSetting('Display/UI/Wallpaper/Distance') \* getSetting('Display/UI/Testing Size Multiplier'), 4), material ); wallpaper.scale.x = -1; wallpaper.name = "wallpaper"; scene.add(wallpaper); }); \ // Setup cameras cameraViewDistance = getSetting('Display/UI/Distance') \* getSetting('Display/UI/Testing Size Multiplier') \* 2; // Keep this down to destroy lag leftCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance); rightCamera = new THREE.PerspectiveCamera(80, 1, 0.001, cameraViewDistance); \ // Setup renderers leftRenderer = new THREE.WebGLRenderer({canvas: byId('left-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true}); rightRenderer = new THREE.WebGLRenderer({canvas: byId('right-canvas'), antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true, alpha: true}); \ updateCameraAspectRatio(); setRendererSize(); \ window\.addEventListener('resize', function() { updateCameraAspectRatio(); setRendererSize(); }); \ // Setup control center const baseY = getSetting('Display/Control Center/Switch To Bottom') ? -100 : 100; const backgroundFolder = getSetting('Display/Control Center/Icon Folder'); function createTileGrid(scale, x, y, farness, tiles, name) { let counter = 0; log(tiles) addWidget({ position: {'x': x, 'y': baseY + y}, size: {'x': 3 \* scale, 'y': 3 \* scale}, background: "hsl(" + getSetting('Quick Settings/Theme Hue') + ", 50%, 80%)", name: name + ' background', radius: 0.5 \* scale, onhover: openTileGroup(name) }); for(let widgetY = 1; widgetY >= -1; widgetY--) { for(let widgetX = -1; widgetX <=1; widgetX++) { if(counter < tiles.length) { if(tiles\[counter].folder) { createTileGrid(scale / 3, (widgetX \* scale), (widgetY \* scale), farness \* 0.99, tiles\[counter].children); } else { log('scale' + scale) addWidget({ position: {'x': x + (widgetX \* scale), 'y': baseY + y + (widgetY \* scale)}, size: {'x': scale, 'y': scale}, background: backgroundFolder + '/' + tiles\[counter].icon + '.svg', name: tiles\[counter].name, group: name, radius: scale / 3, distance: farness \* 0.99 }); log('added widget control center') }; } else { break; }; counter++; }; if(counter >= tiles.length) { break; }; }; }; \ createTileGrid( getSetting('Display/UI/Scale/Control Center') \* 1.5, 0, baseY, 1, getSetting('Display/Control Center/Tiles'), getSetting('Display/Control Center/Tiles', false)\['name'] ); // Quick function let quickFunction = getSetting('Display/Control Center/Quick Function', false); \ addWidget({ position: {'x': 0, 'y': getSetting('Display/Control Center/Switch To Bottom') ? 100 : -100}, background: '/assets/images/icons/control\_center/' + quickFunction.icon + '.svg', name: quickFunction.name, onclick: quickFunction.onclick }); \ // testing addWidget({ position: {'x': 0, 'y': -50}, background: '/assets/images/icons/control\_center/torch.svg', name: "torch" }); addWidget({ position: {'x': 200, 'y': 10}, background: '/assets/images/icons/control\_center/screencast.svg', name: "screencast" }); for(let i of range(16)) { addWidget({ position: {'x': i \* 25, 'y': 0}, background: 'hsl(' + getSetting('Quick Settings/Theme Hue') + ', 100%, ' + ((i / 16) \* 100) + '%)', name: "test" + i, textContent: '',//i.toString() 'onclick': function() { log('click' + i); }, 'onhover': function() { log('hover' + i); } }); } }; \ function updateSetting(url, value) { customiseSetting(url, value); \ switch(url) { case 'Display/UI/Wallpaper/Opacity': wallpaper.material.opacity = value; break; }; }; \ // Start \ // Setup the camera stream function setupCameraStream() { function handleSuccess(stream) { cameraStream = document.createElement('video'); cameraStream.style.transform = 'rotate(270deg)'; cameraStream.srcObject = stream; cameraStream.play(); \ let texture = new THREE.VideoTexture(cameraStream); texture.minFilter = THREE.LinearFilter; texture.magFilter = THREE.LinearFilter; scene.background = texture; \ customiseSetting('Display/UI/Wallpaper/Opacity', 0); // Temporary until GUI settings are introduced } \ function handleError(error) { // Set wallpaper opacity to 1 updateSetting('Display/UI/Wallpaper/Opacity', 1); \ log('Unable to access the camera/webcam.'); } \ navigator.mediaDevices.getUserMedia({video: {facingMode: "environment"}}) .then(handleSuccess) .catch(function(error) { if (error.name === 'OverconstrainedError') { // Fallback to default video settings navigator.mediaDevices.getUserMedia({video: true}) .then(handleSuccess) .catch(handleError); } else { // Handle other errors handleError(error); } }); }; \ // Fullscreen and pointer lock, request fullscreen mode for the element function openFullscreen(elem, then) { if (elem.requestFullscreen) { elem.requestFullscreen().then(then); } else if (elem.webkitRequestFullscreen) { /\* Safari \*/ elem.webkitRequestFullscreen().then(then); } else if (elem.msRequestFullscreen) { /\* IE11 \*/ elem.msRequestFullscreen().then(then); } } // Request pointer lock function requestPointerLock(myTargetElement) { const promise = myTargetElement.requestPointerLock({ unadjustedMovement: true, }); \ if (!promise) { log("disabling mouse acceleration is not supported"); return; } \ return promise .then(() => log("pointer is locked")) .catch((error) => { if (error.name === "NotSupportedError") { // Some platforms may not support unadjusted movement. // You can request again a regular pointer lock. return myTargetElement.requestPointerLock(); } }); } \ function lockPointer() { requestPointerLock(byId('body')); document.addEventListener("pointerlockchange", lockChangeAlert, false); }; \ function lockChangeAlert() { if (document.pointerLockElement === byId('body')) { document.addEventListener("mousemove", updatePosition, false); } else { document.removeEventListener("mousemove", updatePosition, false); } } \ function updatePosition(e) { onLockedMouseMove(e.movementX, e.movementY); }; \ function fullscreenAndPointerLock() { openFullscreen(byId('body'), function() { lockPointer(); }); } \ function permission() { // Check if the device supports deviceorientation and requestPermission if (typeof(DeviceMotionEvent) !== "undefined" && typeof(DeviceMotionEvent.requestPermission) === "function") { // Request permission DeviceMotionEvent.requestPermission() .then(response => { // Check the response if (response == "granted") {}; }) .catch(console.error); // Handle errors } else { // Device does not support deviceorientation log("DeviceOrientationEvent is not defined"); } } \ async function keepScreenAwake() { // Create a reference for the Wake Lock. let wakeLock = null; \ // create an async function to request a wake lock try { wakeLock = await navigator.wakeLock.request("screen"); log("Wake Lock is active!"); } catch (err) { // The Wake Lock request has failed - usually system related, such as battery. log(\`${err.name}, ${err.message}\`); } } \ let framesSoFar = 0; \ function startFPSCount() { byId('logic-fps').innerHTML = fps.toString(); \ let renderFPSInterval = setInterval(function() { byId('render-fps').innerHTML = framesSoFar.toString(); framesSoFar = 0; }, 1000); } \ function start() { byId('loading-screen').style.display = 'none'; setupCameraStream(); startLogic(); startFPSCount(); render(); permission(); fullscreenAndPointerLock(); keepScreenAwake(); \ byId('left-canvas').addEventListener('click', fullscreenAndPointerLock); byId('right-canvas').addEventListener('click', fullscreenAndPointerLock); }; \ // Loading byId('loading-bar').style.display = 'block'; \ manager.onProgress = function (url, itemsLoaded, itemsTotal) { //log('Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.'); \ byId('loading-bar-fg').style.setProperty('--size', ((itemsLoaded / itemsTotal) \* 100) + '%'); }; \ manager.onError = function (url) { log('There was an error loading ' + url); }; \ manager.onLoad = function ( ) { setTimeout(function() { byId('loading-bar').style.display = 'none'; byId('play').style.display = 'block'; }, 500); byId('play').addEventListener('click', start); }; \ function openTileGroup(group) { } \ // Logic let pointerRaycast = new THREE.Raycaster(); let previousIntersection = pointerRaycast.intersectObjects(scene.children); let clicking = false; \ function logic() { // Set camera rotation if(cameraTargetRotation.x != cameraRotation.x || cameraTargetRotation.y != cameraRotation.y) { setCameraRotation(); }; \ // Update pointer pointerRaycast.set( new THREE.Vector3(0, 0, 0), leftCamera.getWorldDirection(new THREE.Vector3()) ); \ // Check if the pointer is itersecting with any object \ const intersections = pointerRaycast.intersectObjects(scene.children); if (intersections.length > 0) { for(let intersection of intersections) { // If it's intersecting with itself, don't do anything if(intersection.object.name == 'pointer' || intersection.object.name == 'segmentPointer') { return; } else { // If it's intersecting with an object, copy that intersection's position, and start to click const point = intersections\[0].point; pointer.position.copy(point); \ // Truehover if(Object.keys(intersection.object).includes('ontruehover')) { // Prevent hover being continuously trigggered if(previousIntersection.uuid != intersections\[0].uuid) { intersection.object.ontruehover(); } } log('truehover') \ // Start click after grace period, if object is clickable if( Object.keys(intersection.object).includes('onclick') || Object.keys(intersection.object).includes('onhover') || Object.keys(intersection.object).includes('onhoverexit') || Object.keys(intersection.object).includes('onlongpress') ) { let gracePeriod = setTimeout(function() { // onhover if(Object.keys(intersection.object).includes('onhover')) { intersection.object.onhover(); } log('hover') \ // Start click if(Object.keys(intersection.object).includes('onclick') && (!clicking)) { clicking = true; \ let fullness = 0; \ // Manage pointers scene.add(segmentPointer); segmentPointer.position.copy(pointer); scene.remove(pointer); let startClick = setInterval(function() { fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Pre-click duration')); setSegmentPointer( fullness, getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'), intersection.object.rotation ); \ byId('pointer-angle').innerHTML = fullness.toFixed(4); \ // On forfeit let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point); if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) { log('forfeit ' + forfeitDistance) clearInterval(startClick); afterClick(); } \ if(fullness >= PI \* 2) { log('click') intersection.object.onclick(); clearInterval(startClick); \ if(Object.keys(intersection.object).includes('onlongpress')) { // Start longpress fullness = 0; let startLongPress = setInterval(function() { fullness += (PI \* 2) / (fps \* getSetting('Input/Eye Click/Duration/Long-press duration')); setSegmentPointer( fullness, getSetting('Display/UI/Pointer/Size') \* getSetting('Display/UI/Pointer/Clicking Size'), intersection.object.rotation, false ); byId('pointer-angle').innerHTML = fullness.toFixed(4); \ let forfeitDistance = distance3d(point, pointerRaycast.intersectObjects(scene.children)\[0].point); if(forfeitDistance > getSetting('Input/Eye Click/Movement limit')) { log('forfeitlongpress') clearInterval(startLongPress); afterClick(); }; \ // On click if(fullness >= PI \* 2) { intersection.object.onlongpress(); log('longpress') clearInterval(startLongPress); afterClick(); } }, 1000 / fps); } else { afterClick(); } } }, 1000 / fps); }; }, getSetting('Input/Eye Click/Delayed hover duration') \* 1000) return; } else { afterClick(); } \ function afterClick() { // Update previous intersection previousIntersection = intersections; previousIntersection.intersection = intersection; \ // Onhoverexit if(intersection.object.uuid != previousIntersection.intersection.object.uuid) { previousIntersection.object.onhoverexit(); } \ clicking = false; log('afterclick') \ // Change back pointers scene.remove(segmentPointer); scene.add(pointer); \ return; } } } }; }; \ function startLogic() { logicInterval = setInterval(logic, 1000 / fps); }; \ function stopLogic() { clearInterval(logicInterval); cameraStream.pause(); }; \ // Input function onLockedMouseMove(xMotion, yMotion) { cameraTargetRotation.x = confine(cameraTargetRotation.x - (yMotion \* cameraRotationSensitivity), -0.5 \* PI, 0.6 \* PI); if(wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI) != (cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity))) { cameraRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI); }; cameraTargetRotation.y = wrap(cameraTargetRotation.y - (xMotion \* cameraRotationSensitivity), -PI, PI); setCameraRotation(); }; \ // Setup buttons byId('toggle-debug').addEventListener('click', function(event) { if(byId('debug-menu').style.display == 'block') { byId('debug-menu').style.display = 'none'; } else { byId('debug-menu').style.display = 'block'; } }); \ // Keypress manager const keysToPreventDefault = \['alt', '/', 'f1', 'f2', 'f3']; \ function putKeyDown(key) { if(!keysDown.includes(key.toLowerCase())) { keysDown.push(key.toLowerCase()); }; }; \ function putKeyUp(key) { keysDown = removeFromArray(keysDown, \[key.toLowerCase()]); }; \ document.addEventListener('keydown', function(e) { if(keysToPreventDefault.includes(e.key.toLowerCase())) { e.preventDefault(); }; putKeyDown(e.key); }); \ document.addEventListener('keyup', function(e) { putKeyUp(e.key); }); \ // Pointer position document.addEventListener('mousemove', function(e) { pointerPosition = {'x': e.clientX, 'y': e.clientY, 'positions': \[{'clientX': e.clientX, 'clientY': e.clientY}], 'type': 'mouse'}; }); \ document.addEventListener('touchmove', function(e) { pointerPosition = {'x': e.touches\[0].clientX, 'y': e.touches\[0].clientY, 'positions': e.touches, 'type': 'touch'}; }); \ // Gyrometer window\.addEventListener("deviceorientation", function(event) { orientation = { 'absolute': event.absolute, 'alpha': event.alpha, 'beta': event.beta, 'gamma': event.gamma }; \ byId('gyro-absolute').innerHTML = orientation.absolute; byId('gyro-alpha').innerHTML = orientation.alpha.toFixed(2); byId('gyro-beta').innerHTML = orientation.beta.toFixed(2); byId('gyro-gamma').innerHTML = orientation.gamma.toFixed(2); const theOrientation = (window\.offsetWidth > window\.offsetHeight) ? 'landscape' : 'portrait'; \ // If orientation is logged correctly if(!Object.values(orientation).includes(null)) { // Subtract 90deg if in portrait mode if(theOrientation == 'portrait') { orientation.alpha = wrap(orientation.alpha + 90, 0, 360); } \ // Offset y const offsetY = 89.5; // I have no idea why this works \ if(Math.abs(orientation.beta) < 100) { cameraTargetRotation.x = (-orientation.gamma \* (PI / 180) % 180) - offsetY; } else { cameraTargetRotation.x = (orientation.gamma \* (PI / 180) % 180) + offsetY; } cameraTargetRotation.y = orientation.alpha \* (PI / 180); cameraTargetRotation.z = (-orientation.beta \* (PI / 180)) + offsetY; \ cameraRotation.x = cameraTargetRotation.x; cameraRotation.y = cameraTargetRotation.y; cameraRotation.z = cameraTargetRotation.z; \ if(theOrientation == 'landscape') { } \ setCameraRotation(); }; }, true);
cross-posted from: https://lemm.ee/post/46066494
I followed the recommended processes for adding images to my app, and it is being displayed correctly on the layout preview, but not at all on the app. I have vector assets, webp, png images, but none are being displayed.
The project is too big to put here in its entirety, but please ask for any snippets that could help you solve the issue. I've tried searching the web and asking LLMs and neither could help, so please help me, fellow humans.
Rider is the best C# IDE IMO, works on linux, mac, and of course Windows. Very happy it's now free!
cross-posted from: https://lemmy.ml/post/21158241
We are thrilled to announce the release of Qt 6.8, packed with support for new desktop, mobile, and embedded platforms, hundreds of improvements, and exciting new features to boost your development experience and meet the needs of demanding applications.
For this release we have focused on improving and stabilizing existing functionality. With over 500 bug fixes and performance improvements since Qt 6.7, your existing code will run better without changing a single line. On macOS, Qt Quick applications now integrate with the native menu bar, and for a native Windows 11 look they can use the new Fluent style. Resizing Quick windows is snappier on macOS with Qt 6.8, and on Windows the application start-up time has been improved by changing the default font database to DirectWrite.
Several modules that were under technology preview have been completed: Qt Graphs, Qt HttpServer, and Qt GRPC are promoted to be fully supported from this release on. Thanks to the feedback from our users we were able to finish those modules with substantial improvements since their initial introduction as technology previews.
cross-posted from: https://lemmy.pierre-couy.fr/post/678825
Hi ! I've been working on this article for the past few days. It would mean a lot to me if you could provide some feedback.
It is about implementing a physico-chemical simulation as my first attempt to write a shader. The code is surprisingly simple and short (less than 100 lines). The "Prerequisite" and "Update rules" sections, however, may need some adjustments to make them clearer.
Thanks for reading
I wanted to share this because it's just a fun departure away from all the doomscrolling. Enjoy!
Hello my fellow, lemons? I have this problem in my current project I’m out of clue how to approach it. Maybe someone had similar experience and can give an advice.
Our requirements captured in JIRA. Throughout years we accumulated thousands of user stories. Say suppose following naive requirements team knows about:
- Day 1: create home page
- Day 20: create profile page
- Day 50: add green footer to all pages
- Day 100: create admin page Day 150: change footer color to blue
Now I’m doing refactoring (yes, I know, this is the actual problem) on day 400 and noticed that footer on profile page having green footer. Because requirements are just set of individual statements not consolidated with all history of system no one on the team knows why is that, is it bug or requirement did change on day 300 but we cant find it now.
When I worked in Waterfall we had BRD and FRD stating current actual desired state of system which was “reduced” from individual requirements which were coming in throughout project life. When in doubt devs can check FRD and not only know how system expected to behave but also which are other parts of the system that will be affected. How is it in Agile? To my understanding FRD is not a thing in Agile. Do I need to scan through hundreds of tickets and hope I didn’t miss anything every time i’m doing any non-trivial change to system?
Programming
All things programming and coding related. Subcommunity of Technology.
This community's icon was made by Aaron Schneider, under the CC-BY-NC-SA 4.0 license.