9 votes

What programming/technical projects have you been working on?

This is a recurring post to discuss programming or other technical projects that we've been working on. Tell us about one of your recent projects, either at work or personal projects. What's interesting about it? Are you having trouble with anything?

7 comments

  1. [2]
    Apos
    Link
    I've been working some more on my shapes library, I added rectangles to the API snip. I managed to do some really nice anti-aliasing so it looks really smooth. I've also reached a pretty big...

    I've been working some more on my shapes library, I added rectangles to the API snip. I managed to do some really nice anti-aliasing so it looks really smooth.


    I've also reached a pretty big milestone for my map editor. It's designed as an ingame map editor so that I can modify my maps while the game is running. I coded it as it's own project so that I can reuse it for my many projects.

    I can work on infinite maps, have different logic for any item types. For example, some objects generate a sort of nav mesh for pathfinding snip1, snip2. I got a dynamic grid for snapping objects snip3. Undo redo works too.

    Main thing to do now is to work on the interface. I'm working on a UI framework in a separate library. Tomorrow or for the rest of the week, I'd really want to figure out how to do overlapping UI elements for example tooltips, drop down menus, floating windows, etc. The API is inspired by IMGUI. It looks like this: (It's really easy to do dynamic UIs)

    Panel.Push();
    if (Button.Put("Show fun").Clicked) {
        _showFun = !_showFun;
    }
    if (_showFun) {
        Label.Put("This is fun!");
    }
    if (Button.Put("Quit").Clicked) {
        Exit();
    }
    Panel.Pop();
    

    I'd really like to avoid doing predefined "layers", but it's not right away clear how I can manage that. I have a vague idea that I'll need a way for components to reorder themselves internally somehow. btw, something that is shown on top of something else should have it's input be consumed first, but it's rendering happen last.

    4 votes
    1. Apos
      Link Parent
      Added line segments just now to my shape library! snip They were more annoying to add than the other shapes since they go from a point a to a point b and are rounded. I draw the shapes on quads in...

      Added line segments just now to my shape library! snip

      They were more annoying to add than the other shapes since they go from a point a to a point b and are rounded. I draw the shapes on quads in a pixel shader. The line quad had to account for a bunch of extra space to give the line it's required thickness and I had to make sure the shader is working in the right coordinate system.

      1 vote
  2. jordan
    Link
    I wrote a bookmarklet script that opens every external URL on a website such as Tildes, Hackernews, Reddit, Lobsters, and other tech/programming related sites. It took about 30 minutes to code up,...

    I wrote a bookmarklet script that opens every external URL on a website such as Tildes, Hackernews, Reddit, Lobsters, and other tech/programming related sites. It took about 30 minutes to code up, and it basically scans the page for URLs, and then opens them in a bunch of tabs using the window.open() method.

    I wrote it so I could read random articles without having to make the decision (should I click this?). Because the 'should I click?' question is often loaded with subconscious bias and prevents me from opening the link. It's strange because once the article is open in my tabstrip it encourages me to read it.

    3 votes
  3. TemulentTeatotaler
    Link
    I have a parent with vertigo who has had a few particularly bad days. They were worried about the pattern of it, so I put together some code to sift through a Google Takeout dump of their calendar...

    I have a parent with vertigo who has had a few particularly bad days. They were worried about the pattern of it, so I put together some code to sift through a Google Takeout dump of their calendar and put it into a chart.

    Their calendar sometimes makes mentions in the description, sometimes in the summary/title. Sometimes time is recorded, other times not. Other related problems like "ache" were of interest, but that hit results like "Rachel", so I added a way to filter out undesired keywords.

    Setting the bucket size to a number of months was done a bit hackishly, but it worked for getting a quick view of whether there was any seasonal trend.

    The data being not ideal/consistent is a bit of an issue. When they were applying for disability they were much better about keeping track, and the number of minutes spent with vertigo were probably not accurate. The good news is it at least allayed their fears that things were getting worse/hitting earlier.

    I've been liking .NET notebooks a lot. The main thing (and maybe I'm missing something critical) I like compared to Jupyter notebooks is they use markdown as a format. That lets me use richer editors for doing any sort of documentation I might want to, and gives me the option of easily integrating it into something like Obsidian.md to maybe make spaced-repetition/Anki cards or other reference material.

    Here's the chart by event. Another one was by count of the minutes.

    Code (.NET interactive notebook)
    #!markdown
    
    * [Take out calendar](https://takeout.google.com/).
    
    #!csharp
    
    #r "nuget: Ical.Net, *-*"
    #r "nuget: Plotly.NET, *-*"
    #r "nuget: Plotly.NET.Interactive, *-*"
    using Ical.Net;
    using System.IO;
    using Plotly.NET.Interactive;
    using Plotly.NET;
    
    #!csharp
    
    //Set the number of months to use per bucket
    var bucketMonths = 1;
    
    //Set words to include/exclude
    var include = new string[]{
        "vertigo",
        //"ache",
        //"migraine"
    };
    var exclude = new string[]{
        "rachel"
    };
    //Added some testing phrases in case you want to see what would be filtered out!
    var testStrings = new string[]{
        "heavy vertigo", "a vertigo", "rachel", "ache", "headache", "rachel has a headache", "foo"
    };
    var filtered = (from t in testStrings
                    where include.Any(i => t.Contains(i, StringComparison.InvariantCultureIgnoreCase) && 
                        !exclude.Any(e => t.Contains(e, StringComparison.InvariantCultureIgnoreCase)))
                    select t);    
    display(filtered);
    
    #!csharp
    
    //Load calendar
    var calendarPath = @".\Calendar.ics";
    var calendar = Calendar.Load(File.ReadAllText(calendarPath));
    
    //Filter events using description/summary text
    var events = from x in calendar.Events
                where include.Any(i => x.Summary.Contains(i, StringComparison.InvariantCultureIgnoreCase) && 
                !exclude.Any(e => x.Summary.Contains(e, StringComparison.InvariantCultureIgnoreCase))) ||
                include.Any(i => x.Description.Contains(i, StringComparison.InvariantCultureIgnoreCase)  &&
                !exclude.Any(e => x.Description.Contains(e, StringComparison.InvariantCultureIgnoreCase)))
                orderby x.DtStart ascending
                select x;
    
    display($"{"Duration",-20}{"Start",-30}{"Summary",-80}{"Count: " + events.Count(),-10}");
    foreach(var e in events.Take(30)) { 
        display($"{e.Duration,-20}{e.DtStart.Date.ToShortDateString(),-30}{e.Summary,-80}");
    }
    
    #!csharp
    
    var months = events.GroupBy(e => new DateTime(e.DtStart.Year,(e.DtStart.Month-1)/bucketMonths+1,1), ev => ev);
    var x = new List<string>();
    var y = new List<double>();
    var y2 = new List<double>();
    
    foreach(var g in months) {
        x.Add(g.Key.ToShortDateString());
        y.Add(g.Count());   //Number of events
        var minutes = g.Sum(e=> e.Duration.TotalMinutes);
        y2.Add(minutes);
    }
    
    #!csharp
    
    Axis.LinearAxis xAxis = new Axis.LinearAxis();
    xAxis.SetValue("title", "Bucket");
    xAxis.SetValue("showgrid", false);
    xAxis.SetValue("showline", true);
    
    Axis.LinearAxis yAxis = new Axis.LinearAxis();
    yAxis.SetValue("title", "Events");
    yAxis.SetValue("showgrid", true);
    yAxis.SetValue("showline", true);
    
    Axis.LinearAxis y2Axis = new Axis.LinearAxis();
    y2Axis.SetValue("title", "Minutes");
    y2Axis.SetValue("showgrid", true);
    y2Axis.SetValue("showline", true);
    
    Chart.Column<string,double,string>(x, y).WithTitle("By # Event", null).WithX_Axis(xAxis).WithY_Axis(yAxis).WithSize(1400,900).Show();
    //Chart.Column<string,double,string>(x, y2).WithTitle("By Time Recorded", null).WithX_Axis(xAxis).WithY_Axis(y2Axis).WithSize(1300,900).Show();
    
    

    I also made a quick Autohotkey script to manage and handle custom protocols (e.g., "lucky://find-some-file" could route to a script that would search anywhere on your computer and open the best/first match).

    Running the script with no parameters prompts if you want to install/remove it. It backs up the existing handler when replacing it, and restores it when removing it.

    The motivation for this was that hyperlinks are supported in lots of things. If I wanted to have a spreadsheet or website run a script by clicking something this is a lazy (and extremely personal use) way of accomplishing that.

    If you want to override another protocol (e.g., "steam://") with something more flexible that's also possible.

    Code (.ahk)
    #NoTrayIcon
    #NoEnv
    #SingleInstance, force
    SendMode Input 
    SetWorkingDir %A_ScriptDir%
    
    LaunchAsAdmin()
    
    ;Options
    global protocol := "test"   ;Protocol to be handled
    global backup := true       ;Backup existing handler for the protocol if it is different
    global restore := true      ;Restore backup if it exists when removing protocol
    global backupPath := A_ScriptDir . "\Backup.reg"
    
    ;If there are parameters process them in Main
    if(A_Args.Length() > 0) {
        Main()
    }
    ;Otherwise prompt for whether the handler should be created/removed.  First/last use stuff.
    else { 
        CustomProtocolPrompt()
    }
    
    Main() {
        ;Combine args to account for spaces in protocol (e.g., 'test://I want to pass a string with spaces')
        args := ""
        For index, arg in A_Args
            args := args . arg . " "
        ;MsgBox %args%
    
        ;Extract the protocol and the command.  Case-sensitive since protocols are.
        ;This would be relevant if you want to have a single script handle lots of protocols which is not a current feature
        RegExMatch(args, "O)(?<protocol>.+):(//)?(?<command>.+)\s*", Parsed)
        protocol := Parsed["protocol"]
        command :=  Parsed["command"]
        
        ;Your code here...
        MsgBox % command
    }
    
    CustomProtocolPrompt(){
        MsgBox, 4,, Would you like to create the custom handler?  (no removes)
        IfMsgBox No
        {
            DeleteCustomProtocol()
        }
        else IfMsgBox Yes
        {
            CreateCustomProtocol()
        }
    }
    
    CreateCustomProtocol() {
        ;Set up
        baseKey := "HKEY_CLASSES_ROOT\" . protocol      ;Uses full path instead of HKCR for compatibility with regedit exporting
        desc := protocol . ":Custom Protocol Handler"
        iconPath := "C:\Program Files\AutoHotkey\AutoHotkey.exe,`%1"
        iconKey := baseKey . "\DefaultIcon"
        handlerPath := """" . A_AhkPath .  """ """ . A_ScriptFullPath . """ ""%1"""
        handlerKey := baseKey . "\shell\open\command" 
    
        ;Backup checks for whether the path of the current protocol is different
        RegRead, currentHandler, %handlerKey%
        if(currentHandler != "" && backup && currentHandler != handlerPath)  {
            export := "regedit /e """ . backupPath . """ """ . baseKey . """"
            Run %export%
            ;MsgBox, Backed up existing handler
        }      
    
        ;Create handler keys
        RegWrite, REG_SZ, %baseKey%,, %desc%
        RegWrite, REG_SZ, %baseKey%,URL Protocol,""
        RegWrite, REG_SZ, %iconKey%,, %iconPath%
        RegWrite, REG_SZ, %handlerKey%,, %handlerPath%
    
        MsgBox, Created protocol handler "%protocol%".
    }
    
    DeleteCustomProtocol(){
        ;If a backup exists, restore it
        if(restore && FileExist(backupPath)) {
            command := "regedit /s """ . backupPath . """"
            Run %command%
            MsgBox Restored previous protocol handler.
        }
        ;Otherwise delete the handler key
        else {
            baseKey := "HKEY_CLASSES_ROOT\" . protocol 
            RegDelete, %baseKey%
            MsgBox Removed protocol handler.
        }
    }
    
    ; ======================================================================================================================
    ; UrlUnescape() -> https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-urlunescapea
    ; URL_DONT_UNESCAPE_EXTRA_INFO = 0x02000000
    ; URL_UNESCAPE_AS_UTF8         = 0x00040000 (Win 8+)
    ; URL_UNESCAPE_INPLACE         = 0x00100000
    ; ======================================================================================================================
    UrlUnescape(Url, Flags := 0x00100000) {
       Return !DllCall("Shlwapi.dll\UrlUnescape", "Ptr", &Url, "Ptr", 0, "UInt", 0, "UInt", Flags, "UInt") ? Url : ""
    }
    ;https://autohotkey.com/board/topic/46526-run-as-administrator-xpvista7-a-isadmin-params-lib/page-4
    LaunchAsAdmin() {
    	Global 0
    	IfEqual, A_IsAdmin, 1, Return 0
    	Loop, %0%
    		params .= A_Space . %A_Index%
    	DllCall("shell32\ShellExecute" (A_IsUnicode ? "":"A"),uint,0,str,"RunAs",str,(A_IsCompiled ? A_ScriptFullPath
    	: A_AhkPath),str,(A_IsCompiled ? "": """" . A_ScriptFullPath . """" . A_Space) params,str,A_WorkingDir,int,1)
    	ExitApp
    }
    

    A last small project from last weekend was using NAudio to create a commend line tool that can:

    • Say something using TTS or play an audio file, while...
      • Specifying the audio device to output
      • Setting the device or sound volume
      • Adjusting the tempo, pitch, or rate the audio file is played at using SoundTouch
      • Optionally saving the audio as a file in a number of formats instead of playing it.

    I used this and a Foobar2000 component that finds the BPM of songs to convert a number of instrumentals to a desired BPM.

    (and I use it to make my computer yell at me and freeze for 5 minutes when it's time to stretch '_')

    2 votes
  4. balooga
    Link
    Around 6 years ago I wrote a PHP app that fetches RSS feeds and stitches them together into a single feed. At the time I was using a podcast player that I liked, but it was the free version that...

    Around 6 years ago I wrote a PHP app that fetches RSS feeds and stitches them together into a single feed. At the time I was using a podcast player that I liked, but it was the free version that limits the number of feeds you can subscribe to. This was my cheapskate way to overcome the limitation without buying the app, but primarily it was just a good excuse to sharpen my skills.

    I eventually bought the app, and then switched to another app a few years later after the developer moved on to greener pastures. But all this time I've continued to use my aggregator app to manage my subscriptions. I'm not even sure why, tbh. Some part of me likes managing everything in a config.json file instead of my podcatcher's UI, even though it's more of a hassle to SSH into my server to do it. I think I just like the feeling of ownership.

    Anyway, fast-forward to this past week. A couple of the shows I listen to are coming through rewind.website (previously mentioned on Tildes), which is a great way to schedule out reruns of podcasts that have already aired. Unfortunately, I noticed that I hadn't heard any of those shows for a while, and discovered that rewind.website was down! Apparently it had been down for a couple weeks. (It has since come back online, but I had no guarantees that it would, and I was missing my shows.)

    Well, I haven't worked with PHP in years but over the weekend I rolled up my sleeves and implemented my own replay scheduler into the app so I don't have to rely on the third-party service. While I was at it, I added a filter feature so I can skip episodes that match regex. For example some of my shows occasionally run "ad" episodes to promote other podcasts or tease their premium content that's behind a paywall. And some shows like to rerun old episodes occasionally in the regular feed. Now all that stuff I don't want is automatically passed over.

    I was kind of surprised that a quick fix for some "broken" podcasts turned into a real value-add in this ancient project of mine. With the new scheduler and episode filters, I now actually have a reason for continuing to use my app. For the first time in 6 years it might actually be useful for other people besides myself.

    Too bad it's PHP.

    2 votes
  5. [2]
    Wolf
    Link
    I have a rough idea of an app that combines note-taking apps with a book reader. If I make highlights in while reading a book in the app, it turns that highlight into a flash card. So when I'm...

    I have a rough idea of an app that combines note-taking apps with a book reader.

    If I make highlights in while reading a book in the app, it turns that highlight into a flash card. So when I'm done with reading, I can review the flash cards later on. There would also be a functionality to link the flash cards together by concept, regardless of what book the flash cards came from.

    In the future, I want to add a calendar to keep track of my to-be-read pile, a journaling system that works with my flash cards, and something that can archive articles.

    This is a rough idea, I'm not even sure if this fits in this thread, but I want to create a more robust way of keeping track of my thoughts. Since most of my important thoughts come from books and articles online, i.e they are text-based, this kind of app seemed the best way to not only record them but find a way to link them and sift through them.

    Let me know if you guys have any adjustments. I am making this just for myself so I am not too worried about usability.

    2 votes
    1. DataWraith
      Link Parent
      I've used a program called Polar in the past; it does a lot of what you're describing. They've gone a bit far with monetization for my taste, and kept adding useless new features, so I can no...

      I've used a program called Polar in the past; it does a lot of what you're describing.
      They've gone a bit far with monetization for my taste, and kept adding useless new features, so I can no longer recommend them, but I thought I'd mention them as a source of inspiration.

      You may also be able to find more information or apps by using the term incremental reading. I believe the SuperMemo flash card app was the first to pioneer this style of learning.

      1 vote