Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Dynamic Libraries (Windows)
#41
The OpenGL version is now available. It is designed for 64-bit IDEs only. The zip file contains the BI, BM, and BAS files, as well as qbvlc_synchro64.dll.
Since we're using OpenGL, the playback is rendered directly on the surface of the flag Smile

Copy it to your correct directory.


Attached Files
.zip   VideoPlayVLC64_OpenGL.zip (Size: 82.52 KB / Downloads: 18)


Reply
#42
I know you dont like me much but mate! Hats off to ya! WELL DONE! 

AND TO ME we need an Extensions forum or something where these MAJOR extensions can be stored and easily got to...heck even an option in the GUI for packages would be AWESOME!

Heres your new challenge....as for the life off me i cant make it work! 

Code: (Select All)
#include <windows.h>
#include <d3d11.h>
#include <dxgi.h>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")        
// Global Internal State
HWND                    g_hwnd = NULL;
ID3D11Device*           g_pd3dDevice = NULL;
ID3D11DeviceContext*    g_pd3dDeviceContext = NULL;
IDXGISwapChain*         g_pSwapChain = NULL;
ID3D11RenderTargetView* g_pRenderTargetView = NULL;
ID3D11DepthStencilView* g_pDepthStencilView = NULL;
// Input Tracking
bool g_keys[256] = { false };
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY: PostQuitMessage(0); return 0;
        case WM_KEYDOWN: if (wParam < 256) g_keys[wParam] = true; return 0;
        case WM_KEYUP:   if (wParam < 256) g_keys[wParam] = false; return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
extern "C" {
    // 1. Create the Window and store the handle globally
    __declspec(dllexport) void DX_Screen_New(int w, int h, char* title) {
        HINSTANCE hInstance = GetModuleHandle(NULL);
        WNDCLASS wc = { 0 };
        wc.lpfnWndProc = WindowProc;
        wc.hInstance = hInstance;
        wc.lpszClassName = "DXWindowPro";
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        RegisterClass(&wc);
        g_hwnd = CreateWindow("DXWindowPro", title, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                              CW_USEDEFAULT, CW_USEDEFAULT, w, h, NULL, NULL, hInstance, NULL);
    }
    // 2. Initialize DX using the global g_hwnd
    __declspec(dllexport) int DX_Init(int width, int height) {
        if (!g_hwnd) return 0;
        DXGI_SWAP_CHAIN_DESC sd = { 0 };
        sd.BufferCount = 1;
        sd.BufferDesc.Width = width;
        sd.BufferDesc.Height = height;
        sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.OutputWindow = g_hwnd; // Use global handle
        sd.SampleDesc.Count = 1;
        sd.Windowed = TRUE;
        D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3 };
        if (FAILED(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, levels, 3,
            D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, NULL, &g_pd3dDeviceContext))) return 0;
        ID3D11Texture2D* pBackBuffer;
        g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
        g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
        pBackBuffer->Release();
        D3D11_TEXTURE2D_DESC dsd = { 0 };
        dsd.Width = width; dsd.Height = height; dsd.MipLevels = 1; dsd.ArraySize = 1;
        dsd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; dsd.SampleDesc.Count = 1;
        dsd.Usage = D3D11_USAGE_DEFAULT; dsd.BindFlags = D3D11_BIND_DEPTH_STENCIL;
        ID3D11Texture2D* pDepthStencil;
        g_pd3dDevice->CreateTexture2D(&dsd, NULL, &pDepthStencil);
        g_pd3dDevice->CreateDepthStencilView(pDepthStencil, NULL, &g_pDepthStencilView);
        pDepthStencil->Release();
        g_pd3dDeviceContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView);
        return 1;
    }
    __declspec(dllexport) void DX_ProcessEvents() {
        MSG msg;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }
    }
    __declspec(dllexport) void DX_Clear(float r, float g, float b, float a) {
        float color[4] = { r, g, b, a };
        if (g_pd3dDeviceContext) {
            g_pd3dDeviceContext->ClearRenderTargetView(g_pRenderTargetView, color);
            g_pd3dDeviceContext->ClearDepthStencilView(g_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
        }
    }
    __declspec(dllexport) void DX_Present() { if (g_pSwapChain) g_pSwapChain->Present(1, 0); }
   
    __declspec(dllexport) int DX_IsKeyPressed(int vk) { return (vk >= 0 && vk < 256) ? (int)g_keys[vk] : 0; }
    __declspec(dllexport) void DX_Cleanup() {
        if (g_pDepthStencilView) g_pDepthStencilView->Release();
        if (g_pRenderTargetView) g_pRenderTargetView->Release();
        if (g_pSwapChain) g_pSwapChain->Release();
        if (g_pd3dDeviceContext) g_pd3dDeviceContext->Release();
        if (g_pd3dDevice) g_pd3dDevice->Release();
        g_hwnd = NULL;
    }
}

Unseen
Reply
#43
Hmm. DirectX11. Let's open this in a new thread. 

Before you look there, re-read the second sentence in the first post of this thread a few times. In all honesty, that's the first thing I've written publicly here:

The following dynamic libraries are written in collaboration with AI.

I'm going to open a new thread for DirectX11. If there are any other challenges, please post them there.


Reply
#44
This program hides the program icon in the Windows tray on the right (to system items) and also allows you to call it.

Program use Tray.H file

Tray.H:

Code: (Select All)
#ifndef QB64_TRAY_H
#define QB64_TRAY_H

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
#include <stdint.h>
#include <string.h>

/* --- runtime link na Shell_NotifyIconA (žádné linkování navíc) --- */
static HMODULE qb64_tray_shell32 = NULL;
typedef BOOL (WINAPI *qb64_Shell_NotifyIconA_t)(DWORD, PNOTIFYICONDATAA);
static qb64_Shell_NotifyIconA_t qb64_Shell_NotifyIconA_p = NULL;

static NOTIFYICONDATAA qb64_tray_nid;
static int  qb64_tray_icon_added = 0;
static HWND  qb64_tray_hwnd = NULL;
static WNDPROC qb64_tray_oldproc = NULL;
static UINT  qb64_tray_cbmsg = (WM_APP + 0x4B64); /* libovolné "unikátní" číslo */

static void qb64_tray_load_shell_notifyicon(void) {
    if (qb64_Shell_NotifyIconA_p) return;
    qb64_tray_shell32 = LoadLibraryA("shell32.dll");
    if (!qb64_tray_shell32) return;
    qb64_Shell_NotifyIconA_p = (qb64_Shell_NotifyIconA_t)GetProcAddress(qb64_tray_shell32, "Shell_NotifyIconA");
}

static HICON qb64_tray_get_window_icon(HWND hwnd) {
    HICON h = (HICON)SendMessageA(hwnd, WM_GETICON, ICON_SMALL, 0);
    if (!h) h = (HICON)(uintptr_t)GetClassLongPtrA(hwnd, GCLP_HICONSM);
    if (!h) h = (HICON)SendMessageA(hwnd, WM_GETICON, ICON_BIG, 0);
    if (!h) h = (HICON)(uintptr_t)GetClassLongPtrA(hwnd, GCLP_HICON);
    if (!h) h = LoadIconA(NULL, IDI_APPLICATION);
    return h;
}

static void qb64_tray_fill_nid(const char* tip) {
    ZeroMemory(&qb64_tray_nid, sizeof(qb64_tray_nid));
    qb64_tray_nid.cbSize = sizeof(qb64_tray_nid); /* doporučený postup */
    qb64_tray_nid.hWnd = qb64_tray_hwnd;
    qb64_tray_nid.uID  = 1;
    qb64_tray_nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    qb64_tray_nid.uCallbackMessage = qb64_tray_cbmsg;
    qb64_tray_nid.hIcon = qb64_tray_get_window_icon(qb64_tray_hwnd);

    if (tip && tip[0]) {
        strncpy(qb64_tray_nid.szTip, tip, sizeof(qb64_tray_nid.szTip) - 1);
        qb64_tray_nid.szTip[sizeof(qb64_tray_nid.szTip) - 1] = '\0';
    } else {
        qb64_tray_nid.szTip[0] = '\0';
    }
}

static void qb64_tray_add_icon(void) {
    qb64_tray_load_shell_notifyicon();
    if (!qb64_Shell_NotifyIconA_p || !qb64_tray_hwnd) return;

    if (!qb64_tray_icon_added) {
        qb64_Shell_NotifyIconA_p(NIM_ADD, &qb64_tray_nid);
        qb64_tray_icon_added = 1;
    } else {
        qb64_Shell_NotifyIconA_p(NIM_MODIFY, &qb64_tray_nid);
    }
}

static void qb64_tray_delete_icon(void) {
    qb64_tray_load_shell_notifyicon();
    if (!qb64_Shell_NotifyIconA_p || !qb64_tray_hwnd) return;

    if (qb64_tray_icon_added) {
        qb64_Shell_NotifyIconA_p(NIM_DELETE, &qb64_tray_nid);
        qb64_tray_icon_added = 0;
    }
}

static void qb64_tray_restore_window(void) {
    if (!qb64_tray_hwnd) return;
    qb64_tray_delete_icon();
    ShowWindow(qb64_tray_hwnd, SW_RESTORE);
    SetForegroundWindow(qb64_tray_hwnd);
}

static void qb64_tray_hide_window(void) {
    if (!qb64_tray_hwnd) return;
    qb64_tray_add_icon();
    ShowWindow(qb64_tray_hwnd, SW_HIDE);
}

static LRESULT CALLBACK qb64_tray_wndproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (msg == WM_SYSCOMMAND) {
        UINT cmd = (UINT)(wParam & 0xFFF0);
        if (cmd == SC_MINIMIZE) {
            qb64_tray_hide_window();
            return 0;
        }
    }

    if (msg == qb64_tray_cbmsg) {
        if (lParam == WM_LBUTTONDBLCLK || lParam == WM_LBUTTONUP) {
            qb64_tray_restore_window();
            return 0;
        }
    }

    if (msg == WM_DESTROY) {
        qb64_tray_delete_icon();
    }

    return CallWindowProcA(qb64_tray_oldproc, hwnd, msg, wParam, lParam);
}

/* --- API pro QB64PE --- */
extern "C" int Tray_Enable(uintptr_t hwnd, const char* tip) {
    if (!hwnd) return 0;
    qb64_tray_hwnd = (HWND)hwnd;

    if (!qb64_tray_oldproc) {
        qb64_tray_oldproc = (WNDPROC)(uintptr_t)GetWindowLongPtrA(qb64_tray_hwnd, GWLP_WNDPROC);
        SetLastError(0);
        LONG_PTR prev = SetWindowLongPtrA(qb64_tray_hwnd, GWLP_WNDPROC, (LONG_PTR)qb64_tray_wndproc);
        if (prev == 0 && GetLastError() != 0) {
            qb64_tray_oldproc = NULL;
            return 0;
        }
    }

    qb64_tray_fill_nid(tip);
    return 1;
}

extern "C" void Tray_Disable(void) {
    if (!qb64_tray_hwnd) return;

    qb64_tray_delete_icon();

    if (qb64_tray_oldproc) {
        SetWindowLongPtrA(qb64_tray_hwnd, GWLP_WNDPROC, (LONG_PTR)qb64_tray_oldproc);
        qb64_tray_oldproc = NULL;
    }
}

extern "C" void Tray_Hide(void) { qb64_tray_hide_window(); }
extern "C" void Tray_Show(void) { qb64_tray_restore_window(); }

extern "C" void Tray_SetTip(const char* tip) {
    qb64_tray_fill_nid(tip);
    if (qb64_tray_icon_added) qb64_tray_add_icon(); /* MOD */
}

#else
/* Non-Windows: prázdné stuby */
extern "C" int  Tray_Enable(unsigned long long, const char*) { return 0; }
extern "C" void Tray_Disable(void) {}
extern "C" void Tray_Hide(void) {}
extern "C" void Tray_Show(void) {}
extern "C" void Tray_SetTip(const char*) {}
#endif

#endif


Tray.bas:

Code: (Select All)

Option _Explicit

Declare Library "tray"
    Function Tray_Enable% (ByVal hwnd As _Offset, tip As String)
    Sub Tray_Disable
    Sub Tray_Hide
    Sub Tray_Show
    Sub Tray_SetTip (tip As String)
End Declare

_Title "QB64PE Tray Demo"
Screen _NewImage(640, 240, 32)

Dim ok%
ok% = Tray_Enable(_WindowHandle, "My Apllication" + Chr$(0))
If ok% = 0 Then Print "Tray_Enable fail."

Print "H = Hide to tray, S = show, ESC = end"
Dim k$
Do
    k$ = UCase$(InKey$)
    If k$ = "H" Then Tray_Hide
    If k$ = "S" Then Tray_Show
    If k$ = Chr$(27) Then Exit Do
    _Limit 60
Loop

Tray_Disable
System


Reply
#45
System Global Hotkeys Utility

Let me introduce another utility. This one focuses on system-wide keyboard shortcuts (Global Hotkeys) that the program registers upon startup and - crucially - unregisters when the program is closed properly.

Why is this useful?

Imagine a program running in the background, minimized (for example, to the system tray). In this state, it cannot respond to standard keyboard input because it lacks window focus. A perfect example is a Screen Recorder: you need to control it while it's minimized because you don't want the recorder's own interface to be visible in the recording, obstructing the view.

How Windows Handles Hotkeys:
The Windows operating system does not allow programs to simply "take" any shortcut they want. Once a shortcut is registered by an application, no other program can use it. This is why it is extremely important to ensure the program exits gracefully. In QB64PE, this is handled by the _EXIT function. This ensures that the hotkey is unregistered from the system before the process terminates, making it available again for other applications.

The Blacklist
This program includes a "blacklist" for combinations such as Ctrl + Alt + F1 through Ctrl + Alt + F12 Why? Because many of these are already reserved. For instance,
 Ctrl + Alt + F8 is frequently used by Intel graphics drivers, and other system utilities use similar combinations. Remember: a shortcut can only be unregistered by the same application that registered it.


How to test this program:

Startup: When you run the following code, it will automatically find and select the first three available shortcuts and display their combinations to you.
Background Test: Minimize the program to the taskbar, switch to a completely different application (e.g., a web browser or text editor), and press one of the shortcuts shown earlier. You should hear a beep (Sound).
Verification: Maximize the program window again. You will see that it successfully detected and logged the key presses even while it was in the background.

Multi-instance Test: * Run the program; it will list certain shortcuts.
 - Leave it running and launch a second instance of the same program.
 - The second instance will display completely different shortcuts.
 - This happens because the shortcuts from the first instance are still occupied and will remain so until that first instance is closed correctly and unregisters them.
 - This approach is the ideal solution for building system-level applications and background utilities.

Program need hotkey.h file for run!

hotkey.h:

Code: (Select All)
#ifndef QB64_HOTKEY_H
#define QB64_HOTKEY_H

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifndef MOD_NOREPEAT
#define MOD_NOREPEAT 0x4000
#endif

static DWORD qb64_hk_last_error = 0;

static volatile LONG qb64_hk_q_head = 0;
static volatile LONG qb64_hk_q_tail = 0;
static int qb64_hk_queue[64];

typedef struct { int id; int used; } qb64_hk_reg_t;
static qb64_hk_reg_t qb64_hk_regs[64];

static void qb64_hk_push(int id) {
    LONG head = qb64_hk_q_head;
    LONG next = (head + 1) & 63;
    if (next == qb64_hk_q_tail) return; /* full -> drop */
    qb64_hk_queue[head] = id;
    qb64_hk_q_head = next;
}

static int qb64_hk_add_reg(int id) {
    for (int i = 0; i < 64; i++) {
        if (!qb64_hk_regs[i].used) { qb64_hk_regs[i].used = 1; qb64_hk_regs[i].id = id; return 1; }
    }
    return 0;
}

static void qb64_hk_clear_regs(void) {
    for (int i = 0; i < 64; i++) { qb64_hk_regs[i].used = 0; qb64_hk_regs[i].id = 0; }
}

#ifdef __cplusplus
extern "C" {
#endif

/* Zavolej 1x na startu – vytvoří message queue pro thread (PeekMessage to zařídí). */
int Hotkey_Enable(void) {
    qb64_hk_last_error = 0;
    qb64_hk_q_head = qb64_hk_q_tail = 0;
    qb64_hk_clear_regs();

    MSG msg;
    PeekMessageA(&msg, NULL, 0, 0, PM_NOREMOVE); /* vytvoří queue */
    return 1;
}

/* Globální hotkey pro tento thread: WM_HOTKEY bude v thread message queue. */
int Hotkey_Register(int id, unsigned int modifiers, unsigned int vk) {
    qb64_hk_last_error = 0;

    if (RegisterHotKey(NULL, id, modifiers, vk)) {
        qb64_hk_add_reg(id);
        return 1;
    }
    qb64_hk_last_error = GetLastError();
    return 0;
}

void Hotkey_Unregister(int id) {
    UnregisterHotKey(NULL, id);
}

void Hotkey_Disable(void) {
    for (int i = 0; i < 64; i++) {
        if (qb64_hk_regs[i].used) UnregisterHotKey(NULL, qb64_hk_regs[i].id);
    }
    qb64_hk_clear_regs();
    qb64_hk_q_head = qb64_hk_q_tail = 0;
}

/* Poll: vysaje WM_HOTKEY z thread queue a vrátí další ID (0 = nic). */
int Hotkey_Pop(void) {
    MSG msg;
    while (PeekMessageA(&msg, NULL, WM_HOTKEY, WM_HOTKEY, PM_REMOVE)) {
        qb64_hk_push((int)msg.wParam); /* wParam = hotkey ID */
    }

    LONG tail = qb64_hk_q_tail;
    if (tail == qb64_hk_q_head) return 0;
    int id = qb64_hk_queue[tail];
    qb64_hk_q_tail = (tail + 1) & 63;
    return id;
}

unsigned long Hotkey_GetLastError(void) {
    return (unsigned long)qb64_hk_last_error;
}

#ifdef __cplusplus
}
#endif

#else
/* non-windows stubs */
#ifdef __cplusplus
extern "C" {
#endif
int Hotkey_Enable(void) { return 0; }
int Hotkey_Register(int, unsigned int, unsigned int) { return 0; }
void Hotkey_Unregister(int) {}
void Hotkey_Disable(void) {}
int Hotkey_Pop(void) { return 0; }
unsigned long Hotkey_GetLastError(void) { return 0; }
#ifdef __cplusplus
}
#endif
#endif

#endif
hotkey.bas:

Code: (Select All)

Option _Explicit

' ---- hotkey.h API ----
Declare Library "hotkey"
    Function Hotkey_Enable% ()
    Function Hotkey_Register% (ByVal id As Long, ByVal modifiers As Long, ByVal vk As Long)
    Sub Hotkey_Unregister (ByVal id As Long)
    Function Hotkey_Pop% ()
    Function Hotkey_GetLastError~& ()
End Declare

' WinAPI modifikators
Const MOD_ALT = 1
Const MOD_CONTROL = 2
Const MOD_SHIFT = 4
Const MOD_WIN = 8
Const MOD_NOREPEAT = &H4000

' Vk codes (just what we use here)
Const VK_F1 = &H70
Const VK_F12 = &H7B
Const VK_NUMPAD0 = &H60

' for program output
Dim Shared GlobalMods(1 To 32) As Long
Dim Shared GlobalVK(1 To 32) As Long
Dim Shared GlobalKeyText$(1 To 32)




Screen _NewImage(900, 300, 32)
_Title "Auto hotkeys"

If Hotkey_Enable = 0 Then End

SetGlobalKeys 3

Print
Print "Press selected hotkeys. ESC end."
Do
    Dim id As Long
    id = Hotkey_Pop
    If id Then
        Select Case id
            Case 1: Print "Function #1 (ID=1) run: "; GlobalKeyText$(1): Sound 150, .5
            Case 2: Print "Function #2 (ID=2) run: "; GlobalKeyText$(2): Sound 300, .5
            Case 3: Print "Function #3 (ID=3) run: "; GlobalKeyText$(3): Sound 450, .5
        End Select
    End If
    _Limit 200
Loop Until InKey$ = Chr$(27) Or _Exit


'erase it and unregister it
Dim i As Long
For i = 1 To 3
    Hotkey_Unregister i
Next i
System






Function ModName$ (m As Long)
    Dim s As String
    If (m And MOD_CONTROL) Then s = s + "Ctrl+"
    If (m And MOD_ALT) Then s = s + "Alt+"
    If (m And MOD_SHIFT) Then s = s + "Shift+"
    If (m And MOD_WIN) Then s = s + "Win+"
    If Len(s) Then s = Left$(s, Len(s) - 1)
    ModName$ = s
End Function

Function VkName$ (vk As Long)
    ' F1-F12
    If vk >= VK_F1 And vk <= VK_F12 Then
        VkName$ = "F" + LTrim$(Str$(vk - VK_F1 + 1))
        Exit Function
    End If

    ' Numpad 0-9
    If vk >= VK_NUMPAD0 And vk <= (VK_NUMPAD0 + 9) Then
        VkName$ = "Num" + LTrim$(Str$(vk - VK_NUMPAD0))
        Exit Function
    End If

    ' A-Z
    If vk >= &H41 And vk <= &H5A Then VkName$ = Chr$(vk): Exit Function
    ' 0-9
    If vk >= &H30 And vk <= &H39 Then VkName$ = Chr$(vk): Exit Function

    VkName$ = "VK_" + Hex$(vk)
End Function

Function KeyText$ (mods As Long, vk As Long)
    KeyText$ = ModName$(mods) + "+" + VkName$(vk)
End Function

' return -1 if ok, 0 if fail
Function TryRegisterAndStore% (id As Long, mods As Long, vk As Long)
    ' To be on the safe side, if it is called multiple times: unregister the old ID
    Hotkey_Unregister id

    If Hotkey_Register(id, mods, vk) Then
        GlobalMods(id) = mods
        GlobalVK(id) = vk
        GlobalKeyText$(id) = KeyText$(mods, vk)
        TryRegisterAndStore% = -1
    Else
        TryRegisterAndStore% = 0
    End If
End Function

' Main: finds the first n free ones from a predefined order and registers them under ID 1..n
Sub SetGlobalKeys (n As Long)
    Dim modSet(1 To 6) As Long
    ' Order = preference (cleanest/least collision first)
    modSet(1) = MOD_CONTROL Or MOD_ALT Or MOD_NOREPEAT
    modSet(2) = MOD_CONTROL Or MOD_ALT Or MOD_SHIFT Or MOD_NOREPEAT
    modSet(3) = MOD_CONTROL Or MOD_ALT Or MOD_WIN Or MOD_NOREPEAT
    modSet(4) = MOD_CONTROL Or MOD_ALT Or MOD_SHIFT Or MOD_WIN Or MOD_NOREPEAT
    modSet(5) = MOD_CONTROL Or MOD_SHIFT Or MOD_NOREPEAT ' a× pozdýji (bý×nýjÜÝ)
    modSet(6) = MOD_ALT Or MOD_SHIFT Or MOD_NOREPEAT ' a× pozdýji

    ' Candidate keys (no F13+; no "killers" like Ctrl+C etc.)
    ' 1) F-keys (often best for recorders)
    ' 2) Numpad (if user has one)
    ' 3) A few letters that usually have no global meaning (R,P,M,K,J,...)
    Dim vkList(1 To 12 + 10 + 8) As Long
    Dim idx As Long: idx = 0

    Dim vk As Long
    For vk = VK_F12 To VK_F1 Step -1 ' start from F12 down so you can see what is occupied right away
        idx = idx + 1: vkList(idx) = vk
    Next vk

    For vk = VK_NUMPAD0 To (VK_NUMPAD0 + 9)
        idx = idx + 1: vkList(idx) = vk
    Next vk

    ' "safer" letters (not C/V/X/A/S/Z etc.)
    idx = idx + 1: vkList(idx) = Asc("R")
    idx = idx + 1: vkList(idx) = Asc("P")
    idx = idx + 1: vkList(idx) = Asc("M")
    idx = idx + 1: vkList(idx) = Asc("K")
    idx = idx + 1: vkList(idx) = Asc("J")
    idx = idx + 1: vkList(idx) = Asc("U")
    idx = idx + 1: vkList(idx) = Asc("I")
    idx = idx + 1: vkList(idx) = Asc("O")

    Dim need As Long: need = n
    Dim id As Long, mi As Long, vi As Long
    Dim found As Long

    For id = 1 To need
        found = 0
        For mi = 1 To 6
            For vi = 1 To idx
                If IsBlacklisted%(modSet(mi), vkList(vi)) Then
                    ' p°eskoŔ
                Else
                    If TryRegisterAndStore%(id, modSet(mi), vkList(vi)) Then
                        found = -1
                        Exit For
                    End If
                End If
            Next vi
            If found Then Exit For
        Next mi

        If Not found Then
            GlobalKeyText$(id) = "NOT FOUND (all occupied)"
        End If
    Next id

    ' program list to program window
    Cls
    For id = 1 To need
        Print "GlobalKey("; id; ") = "; GlobalKeyText$(id)
    Next id
End Sub

Function IsBlacklisted% (mods As Long, vk As Long) 'disable combinations as CTRL + ALT + Fx (often used with graphic drivers and graphic system calls)
    If (mods And (MOD_CONTROL Or MOD_ALT)) = (MOD_CONTROL Or MOD_ALT) Then
        If vk >= VK_F1 And vk <= VK_F12 Then
            IsBlacklisted% = -1
            Exit Function
        End If
    End If
    IsBlacklisted% = 0
End Function


Reply
#46
This is a utility that allows you to select a date from a calendar. The function returns the selected date as a string.

program need pdate.h file.

pdate.h:

Code: (Select All)
#ifndef QB64PE_PICKDATE_H
#define QB64PE_PICKDATE_H

#ifndef _WIN32
#error "pickdate.h je jen pro Windows."
#endif

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif

// kvůli některým definicím common controls
#ifndef _WIN32_IE
#define _WIN32_IE 0x0501
#endif

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>

#define QB64_PICKDATE_CLASSA "QB64PE_PickDateWindow"
#define IDC_QB64_MONTHCAL  1010
#define IDC_QB64_OK        1
#define IDC_QB64_CANCEL    2

typedef BOOL (WINAPI *PFN_InitCommonControlsEx)(const INITCOMMONCONTROLSEX*);

static void qb64_pickdate_init_comctl_dateclasses(void) {
    // Bezpečně: dynamicky natáhnout comctl32 a zavolat InitCommonControlsEx (bez linkování comctl32)
    HMODULE hComctl = LoadLibraryA("comctl32.dll");
    if (!hComctl) return;

    PFN_InitCommonControlsEx pInit =
        (PFN_InitCommonControlsEx)GetProcAddress(hComctl, "InitCommonControlsEx");
    if (!pInit) return;

    INITCOMMONCONTROLSEX icc;
    ZeroMemory(&icc, sizeof(icc));
    icc.dwSize = sizeof(icc);
    icc.dwICC = ICC_DATE_CLASSES;
    pInit(&icc);
}

typedef struct qb64_pickdate_ctx_s {
    HWND hwnd;
    HWND hCal;
    SYSTEMTIME stInit;
    SYSTEMTIME stSel;
    int hasInit;
    int ok;
} qb64_pickdate_ctx;

static void qb64_pickdate_center_window(HWND hwnd) {
    RECT rc;
    GetWindowRect(hwnd, &rc);
    int w = rc.right - rc.left;
    int h = rc.bottom - rc.top;

    int sw = GetSystemMetrics(SM_CXSCREEN);
    int sh = GetSystemMetrics(SM_CYSCREEN);

    int x = (sw - w) / 2;
    int y = (sh - h) / 2;

    SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
}

static void qb64_pickdate_layout(HWND hwnd, qb64_pickdate_ctx* ctx) {
    // Zjisti minimální potřebný rozměr MonthCal
    RECT r = {0,0,0,0};
    SendMessageA(ctx->hCal, MCM_GETMINREQRECT, 0, (LPARAM)&r);
    int calW = (r.right - r.left);
    int calH = (r.bottom - r.top);
    if (calW <= 0) calW = 220;
    if (calH <= 0) calH = 160;

    const int pad = 10;
    const int btnW = 90;
    const int btnH = 26;
    const int gap = 10;

    int clientW = calW + pad * 2;
    int minBtnRow = (btnW * 2 + gap) + pad * 2;
    if (clientW < minBtnRow) clientW = minBtnRow;

    int calX = pad;
    int calY = pad;

    int btnY = calY + calH + pad;

    int okX = (clientW / 2) - gap/2 - btnW;
    int cancelX = (clientW / 2) + gap/2;

    MoveWindow(ctx->hCal, calX, calY, calW, calH, TRUE);

    HWND hOk = GetDlgItem(hwnd, IDC_QB64_OK);
    HWND hCancel = GetDlgItem(hwnd, IDC_QB64_CANCEL);

    MoveWindow(hOk, okX, btnY, btnW, btnH, TRUE);
    MoveWindow(hCancel, cancelX, btnY, btnW, btnH, TRUE);

    int clientH = btnY + btnH + pad;

    // Přepočet na velikost okna podle stylu
    RECT wr = {0, 0, clientW, clientH};
    DWORD style = (DWORD)GetWindowLongPtr(hwnd, GWL_STYLE);
    DWORD exStyle = (DWORD)GetWindowLongPtr(hwnd, GWL_EXSTYLE);
    AdjustWindowRectEx(&wr, style, FALSE, exStyle);

    int winW = wr.right - wr.left;
    int winH = wr.bottom - wr.top;

    SetWindowPos(hwnd, NULL, 0, 0, winW, winH, SWP_NOZORDER | SWP_NOMOVE);
    qb64_pickdate_center_window(hwnd);
}

static LRESULT CALLBACK qb64_pickdate_wndproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    qb64_pickdate_ctx* ctx = (qb64_pickdate_ctx*)GetWindowLongPtr(hwnd, GWLP_USERDATA);

    switch (msg) {
    case WM_NCCREATE: {
        CREATESTRUCTA* cs = (CREATESTRUCTA*)lParam;
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams);
        return TRUE;
    }
    case WM_CREATE: {
        ctx = (qb64_pickdate_ctx*)GetWindowLongPtr(hwnd, GWLP_USERDATA);

        ctx->hCal = CreateWindowExA(
            0,
            MONTHCAL_CLASSA, "",
            WS_CHILD | WS_VISIBLE | WS_BORDER,
            10, 10, 220, 160,
            hwnd, (HMENU)(INT_PTR)IDC_QB64_MONTHCAL,
            GetModuleHandleA(NULL), NULL
        );

        CreateWindowExA(
            0, "BUTTON", "OK",
            WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
            10, 180, 90, 26,
            hwnd, (HMENU)(INT_PTR)IDC_QB64_OK,
            GetModuleHandleA(NULL), NULL
        );

        CreateWindowExA(
            0, "BUTTON", "Storno",
            WS_CHILD | WS_VISIBLE,
            110, 180, 90, 26,
            hwnd, (HMENU)(INT_PTR)IDC_QB64_CANCEL,
            GetModuleHandleA(NULL), NULL
        );

        if (ctx->hasInit) {
            SendMessageA(ctx->hCal, MCM_SETCURSEL, 0, (LPARAM)&ctx->stInit);
        }

        qb64_pickdate_layout(hwnd, ctx);
        return 0;
    }
    case WM_COMMAND: {
        int id = LOWORD(wParam);

        if (id == IDC_QB64_OK) {
            SYSTEMTIME st;
            ZeroMemory(&st, sizeof(st));
            SendMessageA(ctx->hCal, MCM_GETCURSEL, 0, (LPARAM)&st);
            ctx->stSel = st;
            ctx->ok = 1;
            DestroyWindow(hwnd);
            return 0;
        }

        if (id == IDC_QB64_CANCEL) {
            ctx->ok = 0;
            DestroyWindow(hwnd);
            return 0;
        }

        return 0;
    }
    case WM_CLOSE:
        if (ctx) ctx->ok = 0;
        DestroyWindow(hwnd);
        return 0;
    }

    return DefWindowProcA(hwnd, msg, wParam, lParam);
}

static ATOM qb64_pickdate_register_class(void) {
    static ATOM atom = 0;
    if (atom) return atom;

    WNDCLASSEXA wc;
    ZeroMemory(&wc, sizeof(wc));
    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = qb64_pickdate_wndproc;
    wc.hInstance = GetModuleHandleA(NULL);
    wc.lpszClassName = QB64_PICKDATE_CLASSA;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    atom = RegisterClassExA(&wc);
    return atom;
}

// Exportovaná funkce pro QB64PE:
// outISO = buffer (STRING proměnná), outISOBytes = velikost bufferu, initialISO = "YYYY-MM-DD\0" nebo ""\0
// Return: 1=OK, 0=storno/chyba
int qb64_pickdate(char* outISO, int outISOBytes, const char* initialISO) {
    if (!outISO || outISOBytes <= 0) return 0;
    outISO[0] = 0;

    qb64_pickdate_init_comctl_dateclasses();
    if (!qb64_pickdate_register_class()) return 0;

    qb64_pickdate_ctx ctx;
    ZeroMemory(&ctx, sizeof(ctx));

    // default: dnešní datum
    GetLocalTime(&ctx.stInit);
    ctx.hasInit = 1;

    // pokud je initialISO zadáno jako YYYY-MM-DD, použij ho
    if (initialISO && initialISO[0]) {
        int y=0, m=0, d=0;
        if (sscanf(initialISO, "%d-%d-%d", &y, &m, &d) == 3) {
            if (y >= 1601 && m >= 1 && m <= 12 && d >= 1 && d <= 31) {
                ctx.stInit.wYear  = (WORD)y;
                ctx.stInit.wMonth = (WORD)m;
                ctx.stInit.wDay  = (WORD)d;
            }
        }
    }

    HWND hwnd = CreateWindowExA(
        WS_EX_DLGMODALFRAME,
        QB64_PICKDATE_CLASSA,
        "Vyber datum",
        WS_CAPTION | WS_SYSMENU,
        CW_USEDEFAULT, CW_USEDEFAULT, 300, 260,
        NULL, NULL,
        GetModuleHandleA(NULL),
        &ctx
    );

    if (!hwnd) return 0;

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    // modal-ish smyčka: pumpuje zprávy, dokud okno existuje
    MSG msg;
    while (IsWindow(hwnd) && GetMessageA(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessageA(&msg);
    }

    if (!ctx.ok) return 0;

    // Zapiš výsledek
    int n = snprintf(outISO, (size_t)outISOBytes, "%04u-%02u-%02u",
                    (unsigned)ctx.stSel.wYear, (unsigned)ctx.stSel.wMonth, (unsigned)ctx.stSel.wDay);

    // pojistka NUL
    outISO[outISOBytes - 1] = 0;

    return (n > 0) ? 1 : 0;
}

#endif // QB64PE_PICKDATE_H
pdate.bas:

Code: (Select All)


$Console

Declare CustomType Library "pdate"
    Function PickDate% Alias qb64_pickdate (outISO As String, ByVal outISOBytes As Long, initialISO As String)
End Declare

Dim out$, init$
out$ = Space$(32) ' buffer for "YYYY-MM-DD" + NUL
init$ = ""
ok% = PickDate(out$, Len(out$), init$ + Chr$(0))

If ok% Then
    p = InStr(out$, Chr$(0))
    If p > 0 Then out$ = Left$(out$, p - 1)
    Print "Date selected: "; out$
Else
    Print "Storno or fail."
End If

Sleep
End


Reply
#47
5.1 Sound in QB64PE

Hello, I would like to inquire whether anyone has a 5.1 sound output configured on their PC. I could only perform a partial test because I am limited to a stereo setup. To utilize 5.1, one must activate the 5.1 mode in the sound settings or the dedicated sound card application. This is the initial version.

I know that surround sound was discussed here previously, specifically by @MasterGy, so I have try do something in this way.

Zip file contains 3x BAS, BI file, 32bit and 64bit DLL file, and testings S3M music file. DLL's source files also included.


Attached Files
.zip   X_Audio.zip (Size: 205.33 KB / Downloads: 11)


Reply
#48
(12-27-2025, 01:35 PM)Petr Wrote: 5.1 Sound in QB64PE

Hello, I would like to inquire whether anyone has a 5.1 sound output configured on their PC. I could only perform a partial test because I am limited to a stereo setup. To utilize 5.1, one must activate the 5.1 mode in the sound settings or the dedicated sound card application. This is the initial version.

I know that surround sound was discussed here previously, specifically by @MasterGy, so I have try do something in this way.

Zip file contains 3x BAS, BI file, 32bit and 64bit DLL file, and testings S3M music file. DLL's source files also included.
Yes, I've dealt with it. But it's not a true 4-channel control. Two sound samples are running at the same time. The original, and one modified (without the high notes). And the program changes the balance and the volume ratio of the two sound samples depending on where the observer is.

You can find it here:
https://qb64phoenix.com/forum/showthread.php?tid=2269
Reply
#49
Transparent program window


Code: (Select All)

Declare Dynamic Library "user32"
    Function GetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long)
    Function SetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long, ByVal dwNewLong As _Offset)
    Function SetLayeredWindowAttributes& (ByVal hWnd As _Offset, ByVal crKey As Long, ByVal bAlpha As _Unsigned _Byte, ByVal dwFlags As Long)
    Function SetWindowPos& (ByVal hWnd As _Offset, ByVal hWndInsertAfter As _Offset, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal uFlags As Long)
End Declare

Const GWL_EXSTYLE = -20
Const WS_EX_LAYERED = &H80000
Const LWA_ALPHA = &H2

Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOZORDER = &H4
Const SWP_FRAMECHANGED = &H20


Dim hWnd As _Offset
Dim exStyle As _Offset
Dim opacity As Integer


Screen _NewImage(800, 600, 32)
_Title "QB64PE - transparent program window"

hWnd = _WindowHandle
opacity = 255 ' 0 = full transparent, 255 = not transparent

exStyle = GetWindowLongPtrA(hWnd, GWL_EXSTYLE)
exStyle = exStyle Or WS_EX_LAYERED
N = SetWindowLongPtrA(hWnd, GWL_EXSTYLE, exStyle)

' některé změny stylů se projeví až po SetWindowPos :contentReference[oaicite:3]{index=3}
N = SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOZORDER Or SWP_FRAMECHANGED)

N = SetLayeredWindowAttributes(hWnd, 0, opacity, LWA_ALPHA)

Do
    Locate 1
    Print "Use mousewheel..."
    oldopacity = opacity
    While _MouseInput
        opacity = opacity + _MouseWheel
    Wend
    opacity = _IIf(opacity > 254, 254, opacity)
    opacity = _IIf(opacity < 1, 1, opacity)
    If oldopacity <> opacity Then
        oldopacity = opacity
        N = SetLayeredWindowAttributes(hWnd, 0, opacity, LWA_ALPHA)
    End If

    Line (410, 110)-(710, 400), _RGB32(250, 200, 100), BF
    _Limit 60
Loop

Warning. If you set it as full transparent (0), program is automaticaly minimalized.


Reply
#50
Not rectangle program window

The following 4 programs show that in Windows you are not limited to rectangular or square program windows. How about a circular program window? The first example is the clock program. Yeah. There were those. But - where is the program window?

Code: (Select All)

Option _Explicit


'  Circular window + analog clock demo for QB64PE (Windows)
'
'  Key concept:
'    Windows normally treats a window as a rectangle. You can "shape" the
'    window by assigning it a region (HRGN). Anything outside that region is:
'      - not visible (clipped away)
'      - not hit-testable (mouse clicks fall through to whatever is underneath)
'
'  This program does two important window-level operations:
'    1) Removes the standard window frame (title bar, borders, system buttons)
'      because a non-rectangular region would otherwise cut those parts off.
'    2) Creates a circular region (CreateEllipticRgn) and applies it via
'      SetWindowRgn, turning the window into a circle.


' POINTAPI is used by GetCursorPos()
' It stores the cursor position in SCREEN coordinates (desktop coordinate space).
Type POINTAPI
    x As Long
    y As Long
End Type

' RECT is used by GetWindowRect()
' It stores the window rectangle in SCREEN coordinates: Left/Top/Right/Bottom.
Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type


'========================
' WinAPI declarations (user32.dll)
'========================
Declare Dynamic Library "user32"

    ' GetWindowLongPtrA / SetWindowLongPtrA:
    '  Read/modify window attributes stored inside the HWND.
    '
    '  Important notes:
    '  - On 64-bit Windows, styles and pointers are "pointer-sized".
    '  - In QB64PE, _Offset is used to safely hold pointer-sized values.
    '
    '  nIndex selects what you read/write:
    '    GWL_STYLE  = base (standard) style flags (WS_*)
    '    GWL_EXSTYLE= extended style flags (WS_EX_*)
    Function GetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long)
    Function SetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long, ByVal dwNewLong As _Offset)

    ' SetWindowPos:
    '  Repositions/resizes a window and/or tells Windows to re-evaluate
    '  certain aspects (like frame changes) depending on flags.
    '
    '  We use it for two reasons:
    '    - Apply the changed style immediately (SWP_FRAMECHANGED)
    '    - Optionally position the window (X,Y) and show it (SWP_SHOWWINDOW)
    Function SetWindowPos& (ByVal hWnd As _Offset, ByVal hWndInsertAfter As _Offset, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal uFlags As Long)

    ' SetWindowRgn:
    '  Assigns a clipping region to the window.
    '  The system uses this region to determine:
    '    - what part of the window is visible
    '    - what part receives mouse input
    '
    '  After a successful call, Windows owns the region handle (HRGN).
    '  You must NOT delete it yourself (in C you would normally NOT call DeleteObject).
    Function SetWindowRgn& (ByVal hWnd As _Offset, ByVal hRgn As _Offset, ByVal bRedraw As Long)

    ' GetCursorPos:
    '  Returns current cursor position in SCREEN coordinates.
    '  Used for dragging the window around with the mouse.
    Function GetCursorPos& (lpPoint As POINTAPI)

    ' GetWindowRect:
    '  Returns the window's rectangle in SCREEN coordinates.
    '  Used to remember the original window position before dragging.
    Function GetWindowRect& (ByVal hWnd As _Offset, lpRect As RECT)

End Declare



' GDI declarations (gdi32.dll)
Declare Dynamic Library "gdi32"

    ' CreateEllipticRgn:
    '  Creates an elliptical region that fits inside the given bounding box.
    '  If the bounding box is a square (width == height), the region is a circle.
    '
    '  Parameters are in CLIENT coordinates of the window region:
    '    (0,0) is top-left of the window
    '    (diam, diam) is bottom-right
    '
    '  Returns HRGN handle (pointer-sized) -> stored in _Offset
    Function CreateEllipticRgn%& (ByVal nLeftRect As Long, ByVal nTopRect As Long, ByVal nRightRect As Long, ByVal nBottomRect As Long)

End Declare



' Constants: window style manipulation


' GWL_STYLE selects the standard window style flags (WS_*).
Const GWL_STYLE = -16

' Standard style bits (WS_*):
' These control title bar, borders, and system buttons.
Const WS_CAPTION = &HC00000 ' Title bar area (caption)
Const WS_THICKFRAME = &H40000 ' Resizable border frame
Const WS_MINIMIZEBOX = &H20000 ' Minimize button
Const WS_MAXIMIZEBOX = &H10000 ' Maximize button
Const WS_SYSMENU = &H80000 ' System menu (icon/menu on the title bar)

' WS_POPUP:
' A top-level window without the standard overlapped frame.
' This is the typical base for shaped windows (no title bar to be cut off).
Const WS_POPUP = &H80000000


' Constants: SetWindowPos flags
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOZORDER = &H4

' SWP_FRAMECHANGED:
' Tells Windows: "the frame/style changed; recompute non-client area".
' This is the key flag after changing window styles using SetWindowLongPtrA.
Const SWP_FRAMECHANGED = &H20

' SWP_SHOWWINDOW:
' Ensures the window is shown after style/position changes.
Const SWP_SHOWWINDOW = &H40

Dim diam As Long ' Diameter of the circular window (and client area)
Dim cx As Long, cy As Long ' Center coordinates (client space)
Dim r As Long ' Radius used for drawing the clock face

Dim hWnd As _Offset ' HWND (window handle) - pointer-sized
Dim style As _Offset ' Style bitfield - pointer-sized
Dim hRgn As _Offset ' HRGN (region handle) - pointer-sized
Dim N As Long ' Return values from WinAPI calls (success/failure codes)

' Dragging state variables
Dim dragActive As Long
Dim ptStart As POINTAPI, ptNow As POINTAPI
Dim rcStart As RECT
Dim wndStartLeft As Long, wndStartTop As Long
Dim startX As Long, startY As Long
Dim dx As Long, dy As Long



' Create QB64PE window / backbuffer
diam = 420

' Create a 32-bit image and use it as the screen.
' In QB64PE this becomes the window client area (your drawable surface).
Screen _NewImage(diam, diam, 32)

_Title "Circular Clock (ESC = konec)"

' Obtain HWND of the QB64PE window so we can call WinAPI on it.
hWnd = _WindowHandle



' 1) Convert the normal window into a borderless popup window
'
' Why this matters:
'  If you apply a circular region to a normal "framed" window, Windows will
'  also clip the title bar and borders. The result is visually broken:
'  half a title bar, cut buttons, etc.
'
' So the correct approach for shaped windows is:
'  - remove caption and borders
'  - use WS_POPUP
'  - provide your own close/drag behavior (ESC to close, click-drag to move)


' Read current standard style bits.
style = GetWindowLongPtrA(hWnd, GWL_STYLE)

' Clear the bits that define the standard frame.
'  AND NOT(mask) clears those bits.
style = style And Not (WS_CAPTION Or WS_THICKFRAME Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX Or WS_SYSMENU)

' Add WS_POPUP to make it a clean borderless top-level window.
style = style Or WS_POPUP

' Write the modified style back to the window.
N = SetWindowLongPtrA(hWnd, GWL_STYLE, style)

' Force Windows to re-apply/recalculate the window frame after style change.
' Also set an initial position and size (200,150) and show the window.
'
' Note:
'  Without SWP_FRAMECHANGED, style changes may not visually apply immediately.
N = SetWindowPos(hWnd, 0, 200, 150, diam, diam, SWP_FRAMECHANGED Or SWP_SHOWWINDOW)



' 2) Create and apply a circular window region (HRGN)
'
' CreateEllipticRgn(0,0,diam,diam) makes an ellipse inside the client rectangle.
' Since width == height, that ellipse is a circle.
'
' SetWindowRgn then tells Windows: "this window exists only inside this region".
' Everything outside becomes:
'  - not painted
'  - not clickable (mouse input passes through)
'
' Ownership note (important):
'  After SetWindowRgn succeeds, Windows owns the region object.
'  You should NOT free/delete it yourself.


hRgn = CreateEllipticRgn(0, 0, diam, diam)
N = SetWindowRgn(hWnd, hRgn, -1) ' bRedraw=-1 requests immediate redraw

' Precompute drawing geometry

cx = diam \ 2
cy = diam \ 2
r = (diam \ 2) - 6

dragActive = 0

Do

    ' --- Exit key: ESC ---
    If _KeyHit = 27 Then Exit Do

    ' --- Pump mouse events ---
    ' QB64PE requires consuming _MouseInput in a loop to keep state updated.
    While _MouseInput
        ' Just pumping the input queue; no work needed here.
    Wend


    ' Custom window dragging (since we removed title bar)
    '
    ' When left mouse button is down:
    '  - On first frame: remember cursor position + window top-left
    '  - On subsequent frames: move window by cursor delta using SetWindowPos
    '
    ' Coordinates:
    '  GetCursorPos and GetWindowRect both operate in SCREEN coordinates.
    If _MouseButton(1) Then
        If dragActive = 0 Then
            dragActive = -1

            N = GetCursorPos(ptStart)
            N = GetWindowRect(hWnd, rcStart)

            wndStartLeft = rcStart.Left
            wndStartTop = rcStart.Top

            startX = ptStart.x
            startY = ptStart.y
        Else
            N = GetCursorPos(ptNow)

            dx = ptNow.x - startX
            dy = ptNow.y - startY

            ' Move only (no resize), keep z-order unchanged.
            N = SetWindowPos(hWnd, 0, wndStartLeft + dx, wndStartTop + dy, 0, 0, SWP_NOSIZE Or SWP_NOZORDER)
        End If
    Else
        dragActive = 0
    End If

    ' --- Draw clock into the circular client area ---
    Call DrawClock(cx, cy, r)

    _Display
    _Limit 60

Loop

End



' Analog clock drawing routine
Sub DrawClock (cx As Long, cy As Long, r As Long)
    Dim t As String
    Dim hh As Long, mm As Long, ss As Long
    Dim hour12 As Long

    Dim pi As Double
    Dim aSec As Double, aMin As Double, aHour As Double
    Dim radSec As Double, radMin As Double, radHour As Double

    Dim x1 As Long, y1 As Long, x2 As Long, y2 As Long
    Dim i As Long
    Dim tickIn As Double, tickOut As Double
    Dim angDeg As Double, angRad As Double

    Dim s As String
    Dim tx As Long, ty As Long

    pi = 3.141592653589793#

    Cls , _RGB32(10, 10, 14)

    ' Clock face border + fill
    Circle (cx, cy), r, _RGB32(220, 220, 230)
    Paint (cx, cy), _RGB32(30, 30, 40), _RGB32(220, 220, 230)

    ' Tick marks: 60 markers; every 5th is longer (hour tick)
    For i = 0 To 59
        angDeg = i * 6
        angRad = angDeg * pi / 180#

        tickOut = r * 0.98
        If (i Mod 5) = 0 Then
            tickIn = r * 0.86
        Else
            tickIn = r * 0.92
        End If

        x1 = cx + Sin(angRad) * tickIn
        y1 = cy - Cos(angRad) * tickIn
        x2 = cx + Sin(angRad) * tickOut
        y2 = cy - Cos(angRad) * tickOut

        Line (x1, y1)-(x2, y2), _RGB32(200, 200, 210)
    Next i

    ' Minimal readable numbers
    Call PutNumber(cx, cy, r, 0, "12")
    Call PutNumber(cx, cy, r, 90, "3")
    Call PutNumber(cx, cy, r, 180, "6")
    Call PutNumber(cx, cy, r, 270, "9")

    ' Read current time in HH:MM:SS
    t = Time$
    hh = Val(Left$(t, 2))
    mm = Val(Mid$(t, 4, 2))
    ss = Val(Right$(t, 2))

    hour12 = hh Mod 12

    ' Convert to angles (degrees):
    '  second hand: 6 degrees per second
    '  minute hand: 6 degrees per minute + fractional by seconds
    '  hour hand  : 30 degrees per hour + fractional by minutes/seconds
    aSec = ss * 6#
    aMin = (mm + ss / 60#) * 6#
    aHour = (hour12 + mm / 60# + ss / 3600#) * 30#

    ' Convert degrees -> radians for SIN/COS
    radSec = aSec * pi / 180#
    radMin = aMin * pi / 180#
    radHour = aHour * pi / 180#

    ' Hour hand (shorter, thicker via double line)
    x2 = cx + Sin(radHour) * (r * 0.55)
    y2 = cy - Cos(radHour) * (r * 0.55)
    Line (cx, cy)-(x2, y2), _RGB32(240, 240, 245)
    Line (cx + 1, cy)-(x2 + 1, y2), _RGB32(240, 240, 245)

    ' Minute hand
    x2 = cx + Sin(radMin) * (r * 0.78)
    y2 = cy - Cos(radMin) * (r * 0.78)
    Line (cx, cy)-(x2, y2), _RGB32(230, 230, 235)

    ' Second hand
    x2 = cx + Sin(radSec) * (r * 0.88)
    y2 = cy - Cos(radSec) * (r * 0.88)
    Line (cx, cy)-(x2, y2), _RGB32(255, 80, 80)

    ' Center cap
    Circle (cx, cy), 4, _RGB32(255, 255, 255)
    Paint (cx, cy), _RGB32(255, 255, 255), _RGB32(255, 255, 255)

    ' Digital time at the bottom (inside the circle)
    s = t
    tx = cx - (Len(s) * 4)
    ty = cy + r \ 2
    _PrintString (tx, ty), s
End Sub


Sub PutNumber (cx As Long, cy As Long, r As Long, angDeg As Long, txt As String)
    Dim pi As Double
    Dim angRad As Double
    Dim rr As Double
    Dim x As Long, y As Long

    pi = 3.141592653589793#
    rr = r * 0.70
    angRad = angDeg * pi / 180#

    x = cx + Sin(angRad) * rr
    y = cy - Cos(angRad) * rr

    ' Rough centering for the default QB64 font
    x = x - (Len(txt) * 4)
    y = y - 8

    _PrintMode _KeepBackground
    _PrintString (x, y), txt
End Sub


[Image: image.png]


The second program shows how to do the same thing, with a transparent background. Slow version, notice the edge of the clock. The edge is still visible.

Code: (Select All)

Option _Explicit


'  CIRCULAR CLOCK WINDOW WITH SOFT (ANTI-ALIASED) TRANSPARENT EDGES

'
'  What we want:
'    - A circular "floating" clock window on the desktop.
'    - Smooth/feathered transparency at the outer edge (soft edge),
'      so the background under the window is visible gradually.
'
'  Why SetWindowRgn alone is NOT enough:
'    - SetWindowRgn (HRGN) can only "hard-clip" the window to a shape.
'      The result is a sharp, jagged edge (no alpha blending).
'    - HRGN is binary: a pixel is either inside (fully visible) or outside
'      (fully invisible). No gradual transparency.
'
'  The correct method for smooth transparency in Windows:
'    1) Turn the window into a *layered window* by adding WS_EX_LAYERED.
'      A layered window can be composited by the Desktop Window Manager (DWM)
'      using per-pixel alpha (transparency) information.
'
'    2) Create a 32-bit bitmap (DIB section) that contains your final pixels:
'        - 8-bit Alpha
'        - 8-bit Red
'        - 8-bit Green
'        - 8-bit Blue
'
'    3) Build the image each frame:
'        - Draw the clock normally into an offscreen QB64 image.
'        - For each pixel, compute a circular alpha mask:
'            * alpha = 255 inside the circle
'            * alpha fades from 255 -> 0 within a "feather band" (edgeFeather)
'            * alpha = 0 outside the outer radius
'        - Apply alpha to the color using *premultiplied alpha*:
'            R' = R * alpha / 255
'            G' = G * alpha / 255
'            B' = B * alpha / 255
'
'      Premultiplied alpha is important because UpdateLayeredWindow expects
'      pixels in premultiplied form when AC_SRC_ALPHA is used.
'
'    4) Push the 32-bit pixel buffer into the DIB section memory, then call
'      UpdateLayeredWindow(..., ULW_ALPHA).
'      Windows will composite the window with per-pixel alpha, giving smooth
'      transparent edges.
'
'  Optional: SetWindowRgn is still useful
'    - We keep a circular HRGN mainly for input hit-testing:
'      clicks outside the circle will pass through to windows behind.
'    - Visual soft edge is still done by per-pixel alpha, not by the region.
'
'  Performance note:
'    - This demo does per-pixel CPU work each frame (diam*diam pixels).
'    - For 420x420 it is usually fine. If needed, it can be optimized by
'      precomputing the alpha mask once and reusing it.




' WinAPI / GDI structs


' Used by GetCursorPos() (screen coordinates).
Type POINTAPI
    x As Long
    y As Long
End Type

' Used by UpdateLayeredWindow(): the size of the bitmap.
Type SIZEAPI
    cx As Long
    cy As Long
End Type

' Used by GetWindowRect() (screen coordinates).
Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

' Used by UpdateLayeredWindow() to describe how alpha blending is applied.
Type BLENDFUNCTION
    BlendOp As _Unsigned _Byte ' Usually AC_SRC_OVER
    BlendFlags As _Unsigned _Byte ' Must be 0
    SourceConstantAlpha As _Unsigned _Byte ' Global alpha multiplier (255 = no change)
    AlphaFormat As _Unsigned _Byte ' AC_SRC_ALPHA = per-pixel alpha is present
End Type

' Color entry used inside BITMAPINFO (we only need one dummy entry).
Type RGBQUAD
    rgbBlue As _Unsigned _Byte
    rgbGreen As _Unsigned _Byte
    rgbRed As _Unsigned _Byte
    rgbReserved As _Unsigned _Byte
End Type

' Describes the DIB section format (32-bit, width/height, etc.).
Type BITMAPINFOHEADER
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type

Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors As RGBQUAD
End Type


Declare Dynamic Library "user32"

    ' Get/SetWindowLongPtrA:
    '  Read/write window attributes (styles) stored in the HWND.
    '  We use:
    '    - GWL_STYLE  for standard style bits (WS_*)
    '    - GWL_EXSTYLE for extended style bits (WS_EX_*)
    Function GetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long)
    Function SetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long, ByVal dwNewLong As _Offset)

    ' SetWindowPos:
    '  Moves/resizes a window and/or forces Windows to re-evaluate the frame.
    '  SWP_FRAMECHANGED is critical after changing style flags.
    Function SetWindowPos& (ByVal hWnd As _Offset, ByVal hWndInsertAfter As _Offset, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal uFlags As Long)

    ' SetWindowRgn:
    '  Assigns an HRGN to the window. This clips the window AND affects hit-test.
    '  After success, Windows owns the region object (do not delete it yourself).
    Function SetWindowRgn& (ByVal hWnd As _Offset, ByVal hRgn As _Offset, ByVal bRedraw As Long)

    ' Used for custom dragging (cursor position in screen coords).
    Function GetCursorPos& (lpPoint As POINTAPI)

    ' Used for custom dragging (window rectangle in screen coords).
    Function GetWindowRect& (ByVal hWnd As _Offset, lpRect As RECT)

    ' GetDC/ReleaseDC:
    '  Get a device context. We use the screen DC (hWnd=0) as reference for GDI.
    Function GetDC%& (ByVal hWnd As _Offset)
    Function ReleaseDC& (ByVal hWnd As _Offset, ByVal hDC As _Offset)

    ' UpdateLayeredWindow:
    '  The key function for per-pixel alpha layered windows.
    '  It takes a source DC containing a 32-bit bitmap and composites it to the
    '  target window using alpha (ULW_ALPHA).
    Function UpdateLayeredWindow& (ByVal hWnd As _Offset, ByVal hdcDst As _Offset, pptDst As POINTAPI, psize As SIZEAPI, ByVal hdcSrc As _Offset, pptSrc As POINTAPI, ByVal crKey As Long, pblend As BLENDFUNCTION, ByVal dwFlags As Long)

End Declare


Declare Dynamic Library "gdi32"

    ' CreateEllipticRgn:
    '  Creates an ellipse region inside the rectangle (left,top,right,bottom).
    '  If width == height, it is a circle.
    Function CreateEllipticRgn%& (ByVal nLeftRect As Long, ByVal nTopRect As Long, ByVal nRightRect As Long, ByVal nBottomRect As Long)

    ' CreateCompatibleDC:
    '  Creates a memory DC compatible with a given DC. We will select a DIB into it.
    Function CreateCompatibleDC%& (ByVal hdc As _Offset)
    Function DeleteDC& (ByVal hdc As _Offset)

    ' SelectObject:
    '  Selects a GDI object (bitmap) into a DC and returns previous selection.
    Function SelectObject%& (ByVal hdc As _Offset, ByVal hObject As _Offset)

    ' DeleteObject:
    '  Deletes a GDI object (bitmap, region, etc). Note: region is owned by Windows
    '  after SetWindowRgn success, so we do not delete the region ourselves.
    Function DeleteObject& (ByVal hObject As _Offset)

    ' CreateDIBSection:
    '  Creates a bitmap (DIB) and returns a pointer to its pixel memory (ppvBits).
    '  We use 32bpp BI_RGB (BGRA in memory).
    Function CreateDIBSection%& (ByVal hdc As _Offset, bmi As BITMAPINFO, ByVal iUsage As Long, ppvBits As _Offset, ByVal hSection As _Offset, ByVal dwOffset As Long)

End Declare



' Fast memory copy

Declare Dynamic Library "kernel32"
    ' RtlMoveMemory:
    '  Copies raw bytes from src -> dest. We use it to copy our pixel array
    '  into the DIB section memory (pBits) efficiently.
    Sub RtlMoveMemory (ByVal dest As _Offset, ByVal src As _Offset, ByVal cb As Long)
End Declare


' Indices for GetWindowLongPtr/SetWindowLongPtr
Const GWL_STYLE = -16
Const GWL_EXSTYLE = -20

' Standard window styles (frame/titlebar/system buttons)
Const WS_CAPTION = &HC00000
Const WS_THICKFRAME = &H40000
Const WS_MINIMIZEBOX = &H20000
Const WS_MAXIMIZEBOX = &H10000
Const WS_SYSMENU = &H80000

' Popup window style (borderless top-level window)
Const WS_POPUP = &H80000000

' Extended style for layered windows
Const WS_EX_LAYERED = &H80000

' SetWindowPos flags
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOZORDER = &H4
Const SWP_FRAMECHANGED = &H20
Const SWP_SHOWWINDOW = &H40

' DIB constants
Const BI_RGB = 0
Const DIB_RGB_COLORS = 0

' UpdateLayeredWindow blending constants
Const ULW_ALPHA = &H2
Const AC_SRC_OVER = 0
Const AC_SRC_ALPHA = 1



' Window / clock geometry
Dim diam As Long
Dim cx As Long, cy As Long
Dim rClock As Long

' Window handle and style words
Dim hWnd As _Offset
Dim style As _Offset
Dim exStyle As _Offset

Dim N As Long

' Dragging state
Dim dragActive As Long
Dim ptStart As POINTAPI, ptNow As POINTAPI
Dim rcStart As RECT
Dim wndStartLeft As Long, wndStartTop As Long
Dim startX As Long, startY As Long
Dim dx As Long, dy As Long

' Layered window / DIB objects
Dim hdcScreen As _Offset ' DC for the entire screen (desktop)
Dim hdcMem As _Offset ' Memory DC that will hold our DIB section
Dim hBmp As _Offset ' The DIB section bitmap handle
Dim hBmpOld As _Offset ' Previous bitmap selected into hdcMem
Dim pBits As _Offset ' Pointer to raw DIB pixel memory

Dim bmi As BITMAPINFO
Dim sz As SIZEAPI
Dim ptSrc As POINTAPI
Dim ptDst As POINTAPI
Dim blend As BLENDFUNCTION

' Offscreen QB64 image (we draw the clock here using QB64 primitives)
Dim img As Long
Dim memImg As _MEM

' Pixel buffer:
'  We build a 32-bit AARRGGBB value per pixel in an array,
'  then copy it into the DIB memory (pBits) using RtlMoveMemory.
Dim pixels As _Unsigned Long
Dim pixelCount As Long
Dim bytesCount As Long

' Soft-edge mask parameters
Dim Shared edgeFeather As Long ' Width (in pixels) of the alpha fade band
Dim Shared rEdge As Long ' Outer radius (alpha==0 outside)
Dim Shared rEdge2 As Long ' rEdge squared
Dim Shared rInner As Long ' Inner radius (alpha==255 inside)
Dim Shared rInner2 As Long ' rInner squared

' Loop variables / per-pixel work
Dim x As Long, y As Long
Dim idx As Long
Dim dxp As Long, dyp As Long
Dim d2 As Long
Dim dist As Single
Dim a As Long ' alpha 0..255 for current pixel

' Color channels / packing
Dim c~& ' QB64 pixel read from _MemGet
Dim rr As Long, gg As Long, bb As Long
Dim rp As Long, gp As Long, bp As Long
Dim outpt~&



' Basic setup

diam = 420

Screen _NewImage(diam, diam, 32)
_Title "circular clock - soft edge (ESC = end)"

' HWND of the QB64PE window
hWnd = _WindowHandle

' Create an offscreen image for the clock drawing.
' We will draw into this image, then read pixels from it.
img = _NewImage(diam, diam, 32)
memImg = _MemImage(img)


' 1) Make the QB64PE window borderless (WS_POPUP)
'
' Why:
'  - A shaped/layered window usually looks wrong with a normal title bar.
'  - We remove caption/borders/buttons and replace with WS_POPUP.
'  - Since there is no title bar, we implement dragging manually (mouse).


style = GetWindowLongPtrA(hWnd, GWL_STYLE)

' Remove standard frame bits
style = style And Not (WS_CAPTION Or WS_THICKFRAME Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX Or WS_SYSMENU)

' Add popup style
style = style Or WS_POPUP

N = SetWindowLongPtrA(hWnd, GWL_STYLE, style)



' 2) Enable WS_EX_LAYERED (required for per-pixel alpha)
'
' Without WS_EX_LAYERED:
'  - UpdateLayeredWindow will not provide per-pixel transparency for the window.
' With WS_EX_LAYERED:
'  - The window content can be composited from a 32-bit bitmap with alpha.


exStyle = GetWindowLongPtrA(hWnd, GWL_EXSTYLE)
exStyle = exStyle Or WS_EX_LAYERED
N = SetWindowLongPtrA(hWnd, GWL_EXSTYLE, exStyle)

' Apply style changes and show the window at initial position.
ptDst.x = 200
ptDst.y = 150
N = SetWindowPos(hWnd, 0, ptDst.x, ptDst.y, diam, diam, SWP_FRAMECHANGED Or SWP_SHOWWINDOW)



' 3) Optional: assign a circular window region (HRGN)
'
' Visual soft edge is done by alpha (UpdateLayeredWindow).
' The region is kept mainly for input behavior:
'  - outside the circle, the window does not receive mouse clicks
'  - clicks "fall through" to windows behind


Dim hRgn As _Offset
hRgn = CreateEllipticRgn(0, 0, diam, diam)
N = SetWindowRgn(hWnd, hRgn, -1)


' 4) Create a 32bpp DIB section that will be used as the source bitmap
'    for UpdateLayeredWindow.
'
' Why DIB section:
'  - It gives us a pointer (pBits) to raw pixel memory.
'  - We can fill pixels directly, then Windows can use it for compositing.
'
' Important: top-down DIB
'  - biHeight negative => the first scanline is the top row (y=0).
'  - This matches the natural coordinate system we use in our loops.


hdcScreen = GetDC(0) ' Screen DC (desktop)
hdcMem = CreateCompatibleDC(hdcScreen)

bmi.bmiHeader.biSize = 40
bmi.bmiHeader.biWidth = diam
bmi.bmiHeader.biHeight = -diam ' Top-down DIB
bmi.bmiHeader.biPlanes = 1
bmi.bmiHeader.biBitCount = 32 ' 32bpp
bmi.bmiHeader.biCompression = BI_RGB
bmi.bmiHeader.biSizeImage = diam * diam * 4

pBits = 0
hBmp = CreateDIBSection(hdcScreen, bmi, DIB_RGB_COLORS, pBits, 0, 0)
hBmpOld = SelectObject(hdcMem, hBmp)

' Parameters for UpdateLayeredWindow
sz.cx = diam
sz.cy = diam
ptSrc.x = 0
ptSrc.y = 0

blend.BlendOp = AC_SRC_OVER
blend.BlendFlags = 0
blend.SourceConstantAlpha = 255
blend.AlphaFormat = AC_SRC_ALPHA ' "Source bitmap has per-pixel alpha"



' Geometry for the clock and the soft edge mask
cx = diam \ 2
cy = diam \ 2
rClock = (diam \ 2) - 6

' Soft edge settings:
'  edgeFeather is the width of the fade ring at the outer edge.
'  Small values (4..12) look like anti-aliasing.
'  Large values (like 120) create a huge halo where the whole clock area
'  gradually fades out (often too much, but it is your dial).
edgeFeather = 120

rEdge = (diam \ 2) - 1
rEdge2 = rEdge * rEdge
rInner = rEdge - edgeFeather
rInner2 = rInner * rInner

' Prepare output pixel buffer
pixelCount = diam * diam
bytesCount = pixelCount * 4
ReDim pixels(0 To pixelCount - 1) As _Unsigned Long

dragActive = 0


Do
    If _KeyHit = 27 Then Exit Do

    ' Pump mouse input queue so _MouseButton/_MouseWheel etc. stay current.
    While _MouseInput
    Wend


    ' Custom dragging:
    '  Because the window is WS_POPUP (no title bar), we move it ourselves.
    '  We do NOT call SetWindowPos every frame here:
    '    - ptDst is passed into UpdateLayeredWindow
    '    - UpdateLayeredWindow can reposition the layered window for us



    If _MouseButton(1) Then
        If dragActive = 0 Then
            dragActive = -1
            N = GetCursorPos(ptStart)
            N = GetWindowRect(hWnd, rcStart)

            wndStartLeft = rcStart.Left
            wndStartTop = rcStart.Top

            startX = ptStart.x
            startY = ptStart.y
        Else
            N = GetCursorPos(ptNow)
            dx = ptNow.x - startX
            dy = ptNow.y - startY
            ptDst.x = wndStartLeft + dx
            ptDst.y = wndStartTop + dy
        End If
    Else
        dragActive = 0
    End If



    ' (1) Draw the clock into an offscreen QB64 image
    '
    ' We use normal QB64 drawing primitives. This image is NOT
    ' directly shown. It is only a source of RGB color data.

    _Dest img
    Call DrawClock(cx, cy, rClock)
    _Dest 0



    ' (2) Build premultiplied 32-bit pixels for UpdateLayeredWindow
    '
    ' For each pixel:
    '  - Compute alpha based on distance from center:
    '      * 255 inside the inner radius
    '      * fade to 0 across the feather ring
    '      * 0 outside the outer radius
    '
    '  - Read the RGB color from the QB64 offscreen image.
    '
    '  - Premultiply:
    '      rp = rr * alpha / 255
    '      gp = gg * alpha / 255
    '      bp = bb * alpha / 255
    '
    '  - Pack as AARRGGBB in a 32-bit integer.
    '    In memory (little-endian), this becomes BGRA byte order, which is
    '    exactly what the 32bpp BI_RGB DIB expects.

    idx = 0
    For y = 0 To diam - 1
        dyp = y - cy
        For x = 0 To diam - 1
            dxp = x - cx
            d2 = dxp * dxp + dyp * dyp

            ' Alpha mask with a fast squared-distance test:
            '  - avoid Sqr() unless we are in the feather band
            If d2 >= rEdge2 Then
                a = 0
            ElseIf d2 <= rInner2 Then
                a = 255
            Else
                dist = Sqr(d2)
                a = (rEdge - dist) / edgeFeather * 255
                If a < 0 Then a = 0
                If a > 255 Then a = 255
            End If

            If a = 0 Then
                ' Fully transparent pixel -> ARGB = 0
                pixels(idx) = 0
            Else
                ' Read QB64 pixel (32-bit) from the offscreen image memory.
                ' Each pixel is 4 bytes. idx is linear index.
                c~& = _MemGet(memImg, memImg.OFFSET + idx * 4, _Unsigned Long)

                rr = _Red32(c~&)
                gg = _Green32(c~&)
                bb = _Blue32(c~&)

                ' Premultiply RGB by alpha
                rp = (rr * a + 127) \ 255
                gp = (gg * a + 127) \ 255
                bp = (bb * a + 127) \ 255

                ' Pack to AARRGGBB (numeric). In memory this is BGRA bytes.
                outpt~& = (a * &H1000000~&) Or (rp * &H10000~&) Or (gp * &H100~&) Or bp

                pixels(idx) = outpt~&
            End If

            idx = idx + 1
        Next x
    Next y


    ' Copy our array into the DIB section memory. This is the buffer
    ' UpdateLayeredWindow will read from.
    Call RtlMoveMemory(pBits, _Offset(pixels(0)), bytesCount)



    ' (3) Present the layered window (per-pixel alpha)
    '
    ' UpdateLayeredWindow composites the source bitmap (hdcMem/hBmp)
    ' into our window at position ptDst using alpha (ULW_ALPHA).

    N = UpdateLayeredWindow(hWnd, hdcScreen, ptDst, sz, hdcMem, ptSrc, 0, blend, ULW_ALPHA)

    _Limit 30
Loop



' Cleanup (GDI + QB64 memory)
N = SelectObject(hdcMem, hBmpOld)
N = DeleteObject(hBmp)
N = DeleteDC(hdcMem)
N = ReleaseDC(0, hdcScreen)

_MemFree memImg
_FreeImage img

End

Sub DrawClock (cx As Long, cy As Long, r As Long) 'the same
    Dim t As String
    Dim hh As Long, mm As Long, ss As Long
    Dim hour12 As Long

    Dim pi As Double
    Dim aSec As Double, aMin As Double, aHour As Double
    Dim radSec As Double, radMin As Double, radHour As Double

    Dim x1 As Long, y1 As Long, x2 As Long, y2 As Long
    Dim i As Long
    Dim tickIn As Double, tickOut As Double
    Dim angDeg As Double, angRad As Double

    Dim s As String
    Dim tx As Long, ty As Long

    pi = 3.141592653589793#

    Cls , _RGB32(10, 10, 14)

    Circle (cx, cy), r, _RGB32(220, 220, 230)
    Paint (cx, cy), _RGB32(30, 30, 40), _RGB32(220, 220, 230)

    For i = 0 To 59
        angDeg = i * 6
        angRad = angDeg * pi / 180#

        tickOut = r * 0.98
        If (i Mod 5) = 0 Then
            tickIn = r * 0.86
        Else
            tickIn = r * 0.92
        End If

        x1 = cx + Sin(angRad) * tickIn
        y1 = cy - Cos(angRad) * tickIn
        x2 = cx + Sin(angRad) * tickOut
        y2 = cy - Cos(angRad) * tickOut

        Line (x1, y1)-(x2, y2), _RGB32(200, 200, 210)
    Next i

    Call PutNumber(cx, cy, r, 0, "12")
    Call PutNumber(cx, cy, r, 90, "3")
    Call PutNumber(cx, cy, r, 180, "6")
    Call PutNumber(cx, cy, r, 270, "9")

    t = Time$
    hh = Val(Left$(t, 2))
    mm = Val(Mid$(t, 4, 2))
    ss = Val(Right$(t, 2))

    hour12 = hh Mod 12

    aSec = ss * 6#
    aMin = (mm + ss / 60#) * 6#
    aHour = (hour12 + mm / 60# + ss / 3600#) * 30#

    radSec = aSec * pi / 180#
    radMin = aMin * pi / 180#
    radHour = aHour * pi / 180#

    x2 = cx + Sin(radHour) * (r * 0.55)
    y2 = cy - Cos(radHour) * (r * 0.55)
    Line (cx, cy)-(x2, y2), _RGB32(240, 240, 245)
    Line (cx + 1, cy)-(x2 + 1, y2), _RGB32(240, 240, 245)

    x2 = cx + Sin(radMin) * (r * 0.78)
    y2 = cy - Cos(radMin) * (r * 0.78)
    Line (cx, cy)-(x2, y2), _RGB32(230, 230, 235)

    x2 = cx + Sin(radSec) * (r * 0.88)
    y2 = cy - Cos(radSec) * (r * 0.88)
    Line (cx, cy)-(x2, y2), _RGB32(255, 80, 80)

    Circle (cx, cy), 4, _RGB32(255, 255, 255)
    Paint (cx, cy), _RGB32(255, 255, 255), _RGB32(255, 255, 255)

    s = t
    tx = cx - (Len(s) * 4)
    ty = cy + r / 2
    _PrintString (tx, ty), s
End Sub


Sub PutNumber (cx As Long, cy As Long, r As Long, angDeg As Long, txt As String)
    Dim pi As Double
    Dim angRad As Double
    Dim rr As Double
    Dim x As Long, y As Long

    pi = 3.141592653589793#
    rr = r * 0.70
    angRad = angDeg * pi / 180#

    x = cx + Sin(angRad) * rr
    y = cy - Cos(angRad) * rr

    x = x - (Len(txt) * 4)
    y = y - 8

    _PrintMode _KeepBackground
    _PrintString (x, y), txt
End Sub


[Image: image.png]


The third version already includes a pre-calculated mask, so the program is less demanding on the processor. The clock border has also disappeared.


Code: (Select All)


Option _Explicit


'  CIRCULAR CLOCK WINDOW WITH HALO/FADE TRANSPARENT EDGES (Windows 10/11)
'  ---------------------------------------------------------------------
'
'  GOAL
'    We want a circular clock window where the outer edge is not a hard cut,
'    but a smooth "halo" fade: fully opaque inside, gradually transparent
'    towards the outside, so the desktop behind is visible through the edge.
'
'  WHY SetWindowRgn ALONE IS NOT ENOUGH
'    SetWindowRgn (HRGN) can only hard-clip the window to a shape:
'      - inside = fully visible
'      - outside = fully invisible
'    There is no per-pixel alpha, so no smooth fading.
'
'  THE CORRECT METHOD: LAYERED WINDOW + UpdateLayeredWindow (per-pixel alpha)
'    1) Make the window WS_EX_LAYERED.
'      This allows Windows DWM to composite the window from a 32-bit bitmap
'      using alpha per pixel (0..255).
'
'    2) Create a 32bpp DIB section (CreateDIBSection) and obtain pointer pBits.
'      That is raw pixel memory that Windows will read from.
'
'    3) Each frame:
'        a) Draw clock normally into an offscreen QB64 image (img).
'        b) Read its pixels (we copy them into srcPixels()).
'        c) For each pixel compute final alpha from a precomputed alpha mask:
'              alphaMask(idx) = 0..255
'            This mask encodes the circular halo/fade.
'        d) Premultiply RGB by alpha (required by UpdateLayeredWindow):
'              R' = R * A / 255
'              G' = G * A / 255
'              B' = B * A / 255
'        e) Store final pixel as AARRGGBB (in memory this becomes BGRA).
'        f) Copy the final pixel buffer into pBits.
'        g) Call UpdateLayeredWindow(..., ULW_ALPHA).
'
'  PRECOMPUTING THE HALO MASK (IMPORTANT OPTIMIZATION)
'    The circular alpha mask depends only on:
'      - window size (diam)
'      - center (cx, cy)
'      - outer radius rEdge
'      - feather width edgeFeather
'    These are constant, so alphaMask(idx) can be computed ONCE at startup.
'    Then each frame we reuse alphaMask(idx) without doing sqrt() repeatedly.
'
'  OPTIONAL: SetWindowRgn still helps
'    Even though visuals come from per-pixel alpha, we keep a circular region
'    so that mouse clicks outside the circle fall through to windows behind.



Type POINTAPI
    x As Long
    y As Long
End Type

Type SIZEAPI
    cx As Long
    cy As Long
End Type

Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Type BLENDFUNCTION
    BlendOp As _Unsigned _Byte
    BlendFlags As _Unsigned _Byte
    SourceConstantAlpha As _Unsigned _Byte
    AlphaFormat As _Unsigned _Byte
End Type

Type RGBQUAD
    rgbBlue As _Unsigned _Byte
    rgbGreen As _Unsigned _Byte
    rgbRed As _Unsigned _Byte
    rgbReserved As _Unsigned _Byte
End Type

Type BITMAPINFOHEADER
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type

Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors As RGBQUAD
End Type


Declare Dynamic Library "user32"
    Function GetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long)
    Function SetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long, ByVal dwNewLong As _Offset)

    Function SetWindowPos& (ByVal hWnd As _Offset, ByVal hWndInsertAfter As _Offset, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal uFlags As Long)
    Function SetWindowRgn& (ByVal hWnd As _Offset, ByVal hRgn As _Offset, ByVal bRedraw As Long)

    Function GetCursorPos& (lpPoint As POINTAPI)
    Function GetWindowRect& (ByVal hWnd As _Offset, lpRect As RECT)

    Function GetDC%& (ByVal hWnd As _Offset)
    Function ReleaseDC& (ByVal hWnd As _Offset, ByVal hDC As _Offset)

    Function UpdateLayeredWindow& (ByVal hWnd As _Offset, ByVal hdcDst As _Offset, pptDst As POINTAPI, psize As SIZEAPI, ByVal hdcSrc As _Offset, pptSrc As POINTAPI, ByVal crKey As Long, pblend As BLENDFUNCTION, ByVal dwFlags As Long)
End Declare


Declare Dynamic Library "gdi32"
    Function CreateEllipticRgn%& (ByVal nLeftRect As Long, ByVal nTopRect As Long, ByVal nRightRect As Long, ByVal nBottomRect As Long)

    Function CreateCompatibleDC%& (ByVal hdc As _Offset)
    Function DeleteDC& (ByVal hdc As _Offset)

    Function SelectObject%& (ByVal hdc As _Offset, ByVal hObject As _Offset)
    Function DeleteObject& (ByVal hObject As _Offset)

    Function CreateDIBSection%& (ByVal hdc As _Offset, bmi As BITMAPINFO, ByVal iUsage As Long, ppvBits As _Offset, ByVal hSection As _Offset, ByVal dwOffset As Long)
End Declare


Declare Dynamic Library "kernel32"
    Sub RtlMoveMemory (ByVal dest As _Offset, ByVal src As _Offset, ByVal cb As Long)
End Declare


Const GWL_STYLE = -16
Const GWL_EXSTYLE = -20

Const WS_CAPTION = &HC00000
Const WS_THICKFRAME = &H40000
Const WS_MINIMIZEBOX = &H20000
Const WS_MAXIMIZEBOX = &H10000
Const WS_SYSMENU = &H80000
Const WS_POPUP = &H80000000

Const WS_EX_LAYERED = &H80000

Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOZORDER = &H4
Const SWP_FRAMECHANGED = &H20
Const SWP_SHOWWINDOW = &H40

Const BI_RGB = 0
Const DIB_RGB_COLORS = 0

Const ULW_ALPHA = &H2
Const AC_SRC_OVER = 0
Const AC_SRC_ALPHA = 1


Dim diam As Long
Dim cx As Long, cy As Long
Dim rClock As Long

Dim hWnd As _Offset
Dim style As _Offset
Dim exStyle As _Offset
Dim hRgn As _Offset

Dim N As Long

' Dragging
Dim dragActive As Long
Dim ptStart As POINTAPI, ptNow As POINTAPI
Dim rcStart As RECT
Dim wndStartLeft As Long, wndStartTop As Long
Dim startX As Long, startY As Long
Dim dx As Long, dy As Long

' Layered window / DIB
Dim hdcScreen As _Offset
Dim hdcMem As _Offset
Dim hBmp As _Offset
Dim hBmpOld As _Offset
Dim pBits As _Offset

Dim bmi As BITMAPINFO
Dim sz As SIZEAPI
Dim ptSrc As POINTAPI
Dim ptDst As POINTAPI
Dim blend As BLENDFUNCTION

' Offscreen QB64 image
Dim img As Long
Dim memImg As _MEM

' Source pixels (raw clock drawing copied from QB64 image)
Dim srcPixels As _Unsigned Long

' Output pixels (premultiplied + alpha mask applied)
Dim pixels As _Unsigned Long

Dim pixelCount As Long
Dim bytesCount As Long

' Precomputed alpha mask (halo/fade)
Dim alphaMask As _Unsigned _Byte

' Mask geometry parameters
Dim edgeFeather As Long
Dim rEdge As Long
Dim rEdge2 As Long
Dim rInner As Long
Dim rInner2 As Long

' Loop variables (kept here to respect "no DIM inside loops")
Dim idx As Long
Dim x As Long, y As Long

Dim a As Long
Dim c~&
Dim rr As Long, gg As Long, bb As Long
Dim rp As Long, gp As Long, bp As Long
Dim outpt~&



' Setup
diam = 420
Screen _NewImage(diam, diam, 32)
_Title "Circular Clock - no border - HALO/Fade (ESC = end)"

hWnd = _WindowHandle

' Offscreen image where we draw the clock using QB64 primitives.
img = _NewImage(diam, diam, 32)
memImg = _MemImage(img)

' Pixel counts (used for copying blocks of memory)
pixelCount = diam * diam
bytesCount = pixelCount * 4

ReDim srcPixels(0 To pixelCount - 1) As _Unsigned Long
ReDim pixels(0 To pixelCount - 1) As _Unsigned Long
ReDim alphaMask(0 To pixelCount - 1) As _Unsigned _Byte



' 1) Make the window borderless (WS_POPUP)
style = GetWindowLongPtrA(hWnd, GWL_STYLE)
style = style And Not (WS_CAPTION Or WS_THICKFRAME Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX Or WS_SYSMENU)
style = style Or WS_POPUP
N = SetWindowLongPtrA(hWnd, GWL_STYLE, style)


' 2) Enable layered window (WS_EX_LAYERED) so per-pixel alpha is possible
exStyle = GetWindowLongPtrA(hWnd, GWL_EXSTYLE)
exStyle = exStyle Or WS_EX_LAYERED
N = SetWindowLongPtrA(hWnd, GWL_EXSTYLE, exStyle)

' Apply style changes and show at initial position
ptDst.x = 200
ptDst.y = 150
N = SetWindowPos(hWnd, 0, ptDst.x, ptDst.y, diam, diam, SWP_FRAMECHANGED Or SWP_SHOWWINDOW)


' 3) Optional: region for hit-test (click-through outside the circle)
hRgn = CreateEllipticRgn(0, 0, diam, diam)
N = SetWindowRgn(hWnd, hRgn, -1)


' 4) Create DIB section (32bpp) that UpdateLayeredWindow will use
hdcScreen = GetDC(0)
hdcMem = CreateCompatibleDC(hdcScreen)

bmi.bmiHeader.biSize = 40
bmi.bmiHeader.biWidth = diam
bmi.bmiHeader.biHeight = -diam ' top-down DIB
bmi.bmiHeader.biPlanes = 1
bmi.bmiHeader.biBitCount = 32
bmi.bmiHeader.biCompression = BI_RGB
bmi.bmiHeader.biSizeImage = bytesCount

pBits = 0
hBmp = CreateDIBSection(hdcScreen, bmi, DIB_RGB_COLORS, pBits, 0, 0)
hBmpOld = SelectObject(hdcMem, hBmp)

sz.cx = diam
sz.cy = diam
ptSrc.x = 0
ptSrc.y = 0

blend.BlendOp = AC_SRC_OVER
blend.BlendFlags = 0
blend.SourceConstantAlpha = 255
blend.AlphaFormat = AC_SRC_ALPHA



' Clock geometry + HALO mask geometry
cx = diam \ 2
cy = diam \ 2
rClock = (diam \ 2) - 6

' HALO/Fade settings:
'  edgeFeather defines how wide the fade band is.
'  Large values produce a big halo; small values produce only slight AA.
edgeFeather = 120

' Outer edge radius (fully transparent outside)
rEdge = (diam \ 2) - 1
rEdge2 = rEdge * rEdge

' Inner radius (fully opaque inside). Clamp to 0 in case feather is huge.
rInner = rEdge - edgeFeather
If rInner < 0 Then rInner = 0
rInner2 = rInner * rInner

' Build alpha mask ONCE (no per-frame sqrt needed)
Call BuildAlphaMask(alphaMask(), diam, cx, cy, rInner2, rEdge2, rInner, rEdge, edgeFeather)

dragActive = 0


Do
    If _KeyHit = 27 Then Exit Do

    While _MouseInput
    Wend


    ' Custom window dragging (left mouse button), since WS_POPUP has no titlebar
    If _MouseButton(1) Then
        If dragActive = 0 Then
            dragActive = -1
            N = GetCursorPos(ptStart)
            N = GetWindowRect(hWnd, rcStart)
            wndStartLeft = rcStart.Left
            wndStartTop = rcStart.Top
            startX = ptStart.x
            startY = ptStart.y
        Else
            N = GetCursorPos(ptNow)
            dx = ptNow.x - startX
            dy = ptNow.y - startY
            ptDst.x = wndStartLeft + dx
            ptDst.y = wndStartTop + dy
        End If
    Else
        dragActive = 0
    End If


    ' 1) Draw the clock into the offscreen QB64 image (img)
    _Dest img
    Call DrawClock(cx, cy, rClock)
    _Dest 0

    ' Copy raw pixels from QB64 offscreen image into srcPixels() in one go.
    ' This avoids calling _MemGet per pixel (usually faster).
    Call RtlMoveMemory(_Offset(srcPixels(0)), memImg.OFFSET, bytesCount)


    ' 2) Apply halo alpha + premultiply RGB into output pixel buffer
    '
    ' alphaMask(idx) already contains 0..255:

    '  - 255 inside the inner radius
    '  - smooth fade through the feather band
    '  - 0 outside the outer radius
    '--------------- ------------------------------------------------------

    idx = 0
    For y = 0 To diam - 1
        For x = 0 To diam - 1

            a = alphaMask(idx)

            If a = 0 Then
                pixels(idx) = 0
            Else
                c~& = srcPixels(idx)

                rr = _Red32(c~&)
                gg = _Green32(c~&)
                bb = _Blue32(c~&)

                ' Premultiply RGB by alpha (required for AC_SRC_ALPHA)
                rp = (rr * a + 127) \ 255
                gp = (gg * a + 127) \ 255
                bp = (bb * a + 127) \ 255

                ' Pack AARRGGBB (little-endian memory = BGRA bytes)
                outpt~& = (a * &H1000000~&) Or (rp * &H10000~&) Or (gp * &H100~&) Or bp
                pixels(idx) = outpt~&
            End If

            idx = idx + 1
        Next x
    Next y

    ' Copy final pixels into DIB section memory and present
    Call RtlMoveMemory(pBits, _Offset(pixels(0)), bytesCount)
    N = UpdateLayeredWindow(hWnd, hdcScreen, ptDst, sz, hdcMem, ptSrc, 0, blend, ULW_ALPHA)

    _Limit 30
Loop


N = SelectObject(hdcMem, hBmpOld)
N = DeleteObject(hBmp)
N = DeleteDC(hdcMem)
N = ReleaseDC(0, hdcScreen)

_MemFree memImg
_FreeImage img

End



' BuildAlphaMask
'  Precomputes alphaMask(idx) for a circular halo/fade.
'
' Inputs:
'  diam        : window width/height (square)
'  cx, cy      : circle center in pixels
'  rInner2    : inner radius squared (alpha=255 inside)
'  rEdge2      : outer radius squared (alpha=0 outside)
'  rInner      : inner radius (not squared)
'  rEdge      : outer radius
'  edgeFeather : width of fade band (pixels)
'
' Alpha profile:
'  - Inside rInner => A = 255
'  - Outside rEdge => A = 0
'  - Between => smooth fade using smoothstep to reduce visible banding:
'        t = (rEdge - dist) / edgeFeather        (t in 0..1)
'        t = t*t*(3 - 2*t)                        (smoothstep)
'        A = t * 255


Sub BuildAlphaMask (alphaMask() As _Unsigned _Byte, diam As Long, cx As Long, cy As Long, rInner2 As Long, rEdge2 As Long, rInner As Long, rEdge As Long, edgeFeather As Long)

    Dim x As Long, y As Long
    Dim idx As Long
    Dim dxp As Long, dyp As Long
    Dim d2 As Long
    Dim dist As Single
    Dim t As Single
    Dim a As Long

    idx = 0
    For y = 0 To diam - 1
        dyp = y - cy
        For x = 0 To diam - 1
            dxp = x - cx
            d2 = dxp * dxp + dyp * dyp

            If d2 >= rEdge2 Then
                alphaMask(idx) = 0
            ElseIf d2 <= rInner2 Then
                alphaMask(idx) = 255
            Else
                ' Only the fade band needs sqrt()
                dist = Sqr(d2)

                ' Normalize to 0..1 across the feather band
                t = (rEdge - dist) / edgeFeather
                If t < 0! Then t = 0!
                If t > 1! Then t = 1!

                ' Smoothstep easing (visually nicer halo, less "ring banding")
                t = t * t * (3! - 2! * t)

                a = CInt(t * 255!)
                If a < 0 Then a = 0
                If a > 255 Then a = 255

                alphaMask(idx) = a
            End If

            idx = idx + 1
        Next x
    Next y

End Sub


'the same again...
Sub DrawClock (cx As Long, cy As Long, r As Long)
    Dim t As String
    Dim hh As Long, mm As Long, ss As Long
    Dim hour12 As Long

    Dim pi As Double
    Dim aSec As Double, aMin As Double, aHour As Double
    Dim radSec As Double, radMin As Double, radHour As Double

    Dim x1 As Long, y1 As Long, x2 As Long, y2 As Long
    Dim i As Long
    Dim tickIn As Double, tickOut As Double
    Dim angDeg As Double, angRad As Double

    Dim s As String
    Dim tx As Long, ty As Long

    pi = 3.141592653589793#

    Cls , _RGB32(10, 10, 14)

    Circle (cx, cy), r, _RGB32(220, 220, 230)
    Paint (cx, cy), _RGB32(30, 30, 40), _RGB32(220, 220, 230)

    For i = 0 To 59
        angDeg = i * 6
        angRad = angDeg * pi / 180#

        tickOut = r * 0.98
        If (i Mod 5) = 0 Then
            tickIn = r * 0.86
        Else
            tickIn = r * 0.92
        End If

        x1 = cx + Sin(angRad) * tickIn
        y1 = cy - Cos(angRad) * tickIn
        x2 = cx + Sin(angRad) * tickOut
        y2 = cy - Cos(angRad) * tickOut

        Line (x1, y1)-(x2, y2), _RGB32(200, 200, 210)
    Next i

    Call PutNumber(cx, cy, r, 0, "12")
    Call PutNumber(cx, cy, r, 90, "3")
    Call PutNumber(cx, cy, r, 180, "6")
    Call PutNumber(cx, cy, r, 270, "9")

    t = Time$
    hh = Val(Left$(t, 2))
    mm = Val(Mid$(t, 4, 2))
    ss = Val(Right$(t, 2))

    hour12 = hh Mod 12

    aSec = ss * 6#
    aMin = (mm + ss / 60#) * 6#
    aHour = (hour12 + mm / 60# + ss / 3600#) * 30#

    radSec = aSec * pi / 180#
    radMin = aMin * pi / 180#
    radHour = aHour * pi / 180#

    x2 = cx + Sin(radHour) * (r * 0.55)
    y2 = cy - Cos(radHour) * (r * 0.55)
    Line (cx, cy)-(x2, y2), _RGB32(240, 240, 245)
    Line (cx + 1, cy)-(x2 + 1, y2), _RGB32(240, 240, 245)

    x2 = cx + Sin(radMin) * (r * 0.78)
    y2 = cy - Cos(radMin) * (r * 0.78)
    Line (cx, cy)-(x2, y2), _RGB32(230, 230, 235)

    x2 = cx + Sin(radSec) * (r * 0.88)
    y2 = cy - Cos(radSec) * (r * 0.88)
    Line (cx, cy)-(x2, y2), _RGB32(255, 80, 80)

    Circle (cx, cy), 4, _RGB32(255, 255, 255)
    Paint (cx, cy), _RGB32(255, 255, 255), _RGB32(255, 255, 255)

    s = t
    tx = cx - (Len(s) * 4)
    ty = cy + r / 2
    _PrintString (tx, ty), s
End Sub


Sub PutNumber (cx As Long, cy As Long, r As Long, angDeg As Long, txt As String)
    Dim pi As Double
    Dim angRad As Double
    Dim rr As Double
    Dim x As Long, y As Long

    pi = 3.141592653589793#
    rr = r * 0.70
    angRad = angDeg * pi / 180#

    x = cx + Sin(angRad) * rr
    y = cy - Cos(angRad) * rr

    x = x - (Len(txt) * 4)
    y = y - 8

    _PrintMode _KeepBackground
    _PrintString (x, y), txt
End Sub



[Image: image.png]


And finally, the latest version, here you can smoothly change the transparency of the clock edges with the mouse wheel.


Code: (Select All)

Option _Explicit


'  CIRCULAR CLOCK WINDOW WITH HALO/FADE TRANSPARENT EDGES (Windows 10/11)
'  ---------------------------------------------------------------------
'
'  GOAL
'    We want a circular clock window where the outer edge is not a hard cut,
'    but a smooth "halo" fade: fully opaque inside, gradually transparent
'    towards the outside, so the desktop behind is visible through the edge.
'
'  WHY SetWindowRgn ALONE IS NOT ENOUGH
'    SetWindowRgn (HRGN) can only hard-clip the window to a shape:
'      - inside = fully visible
'      - outside = fully invisible
'    There is no per-pixel alpha, so no smooth fading.
'
'  THE CORRECT METHOD: LAYERED WINDOW + UpdateLayeredWindow (per-pixel alpha)
'    1) Make the window WS_EX_LAYERED.
'      This allows Windows DWM to composite the window from a 32-bit bitmap
'      using alpha per pixel (0..255).
'
'    2) Create a 32bpp DIB section (CreateDIBSection) and obtain pointer pBits.
'      That is raw pixel memory that Windows will read from.
'
'    3) Each frame:
'        a) Draw clock normally into an offscreen QB64 image (img).
'        b) Read its pixels (we copy them into srcPixels()).
'        c) For each pixel compute final alpha from a precomputed alpha mask:
'              alphaMask(idx) = 0..255
'            This mask encodes the circular halo/fade.
'        d) Premultiply RGB by alpha (required by UpdateLayeredWindow):
'              R' = R * A / 255
'              G' = G * A / 255
'              B' = B * A / 255
'        e) Store final pixel as AARRGGBB (in memory this becomes BGRA).
'        f) Copy the final pixel buffer into pBits.
'        g) Call UpdateLayeredWindow(..., ULW_ALPHA).
'
'  PRECOMPUTING THE HALO MASK (IMPORTANT OPTIMIZATION)
'    The circular alpha mask depends only on:
'      - window size (diam)
'      - center (cx, cy)
'      - outer radius rEdge
'      - feather width edgeFeather
'    These are constant, so alphaMask(idx) can be computed ONCE at startup.
'    Then each frame we reuse alphaMask(idx) without doing sqrt() repeatedly.
'
'  OPTIONAL: SetWindowRgn still helps
'    Even though visuals come from per-pixel alpha, we keep a circular region
'    so that mouse clicks outside the circle fall through to windows behind.



Type POINTAPI
    x As Long
    y As Long
End Type

Type SIZEAPI
    cx As Long
    cy As Long
End Type

Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Type BLENDFUNCTION
    BlendOp As _Unsigned _Byte
    BlendFlags As _Unsigned _Byte
    SourceConstantAlpha As _Unsigned _Byte
    AlphaFormat As _Unsigned _Byte
End Type

Type RGBQUAD
    rgbBlue As _Unsigned _Byte
    rgbGreen As _Unsigned _Byte
    rgbRed As _Unsigned _Byte
    rgbReserved As _Unsigned _Byte
End Type

Type BITMAPINFOHEADER
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type

Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors As RGBQUAD
End Type


Declare Dynamic Library "user32"
    Function GetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long)
    Function SetWindowLongPtrA%& (ByVal hWnd As _Offset, ByVal nIndex As Long, ByVal dwNewLong As _Offset)

    Function SetWindowPos& (ByVal hWnd As _Offset, ByVal hWndInsertAfter As _Offset, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal uFlags As Long)
    Function SetWindowRgn& (ByVal hWnd As _Offset, ByVal hRgn As _Offset, ByVal bRedraw As Long)

    Function GetCursorPos& (lpPoint As POINTAPI)
    Function GetWindowRect& (ByVal hWnd As _Offset, lpRect As RECT)

    Function GetDC%& (ByVal hWnd As _Offset)
    Function ReleaseDC& (ByVal hWnd As _Offset, ByVal hDC As _Offset)

    Function UpdateLayeredWindow& (ByVal hWnd As _Offset, ByVal hdcDst As _Offset, pptDst As POINTAPI, psize As SIZEAPI, ByVal hdcSrc As _Offset, pptSrc As POINTAPI, ByVal crKey As Long, pblend As BLENDFUNCTION, ByVal dwFlags As Long)
End Declare


Declare Dynamic Library "gdi32"
    Function CreateEllipticRgn%& (ByVal nLeftRect As Long, ByVal nTopRect As Long, ByVal nRightRect As Long, ByVal nBottomRect As Long)

    Function CreateCompatibleDC%& (ByVal hdc As _Offset)
    Function DeleteDC& (ByVal hdc As _Offset)

    Function SelectObject%& (ByVal hdc As _Offset, ByVal hObject As _Offset)
    Function DeleteObject& (ByVal hObject As _Offset)

    Function CreateDIBSection%& (ByVal hdc As _Offset, bmi As BITMAPINFO, ByVal iUsage As Long, ppvBits As _Offset, ByVal hSection As _Offset, ByVal dwOffset As Long)
End Declare


Declare Dynamic Library "kernel32"
    Sub RtlMoveMemory (ByVal dest As _Offset, ByVal src As _Offset, ByVal cb As Long)
End Declare


Const GWL_STYLE = -16
Const GWL_EXSTYLE = -20

Const WS_CAPTION = &HC00000
Const WS_THICKFRAME = &H40000
Const WS_MINIMIZEBOX = &H20000
Const WS_MAXIMIZEBOX = &H10000
Const WS_SYSMENU = &H80000
Const WS_POPUP = &H80000000

Const WS_EX_LAYERED = &H80000

Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOZORDER = &H4
Const SWP_FRAMECHANGED = &H20
Const SWP_SHOWWINDOW = &H40

Const BI_RGB = 0
Const DIB_RGB_COLORS = 0

Const ULW_ALPHA = &H2
Const AC_SRC_OVER = 0
Const AC_SRC_ALPHA = 1


Dim diam As Long
Dim cx As Long, cy As Long
Dim rClock As Long

Dim hWnd As _Offset
Dim style As _Offset
Dim exStyle As _Offset
Dim hRgn As _Offset

Dim N As Long

' Dragging
Dim dragActive As Long
Dim ptStart As POINTAPI, ptNow As POINTAPI
Dim rcStart As RECT
Dim wndStartLeft As Long, wndStartTop As Long
Dim startX As Long, startY As Long
Dim dx As Long, dy As Long

' Layered window / DIB
Dim hdcScreen As _Offset
Dim hdcMem As _Offset
Dim hBmp As _Offset
Dim hBmpOld As _Offset
Dim pBits As _Offset

Dim bmi As BITMAPINFO
Dim sz As SIZEAPI
Dim ptSrc As POINTAPI
Dim ptDst As POINTAPI
Dim blend As BLENDFUNCTION

' Offscreen QB64 image
Dim img As Long
Dim memImg As _MEM

' Source pixels (raw clock drawing copied from QB64 image)
Dim srcPixels As _Unsigned Long

' Output pixels (premultiplied + alpha mask applied)
Dim pixels As _Unsigned Long

Dim pixelCount As Long
Dim bytesCount As Long

' Precomputed alpha mask (halo/fade)
Dim alphaMask As _Unsigned _Byte

Dim gammaEdge As Single
gammaEdge = 0.6! ' <1 = fatter/brighter halo, >1 = tighter halo

' Mask geometry parameters
Dim edgeFeather As Long
Dim rEdge As Long
Dim rEdge2 As Long
Dim rInner As Long
Dim rInner2 As Long

' Loop variables
Dim idx As Long
Dim x As Long, y As Long

Dim a As Long
Dim c~&
Dim rr As Long, gg As Long, bb As Long
Dim rp As Long, gp As Long, bp As Long
Dim outpt~&


Dim Shared oldGammaEdge As Single
Dim Shared gammaStep As Single
Dim Shared gammaMin As Single
Dim Shared gammaMax As Single
Dim Shared wheelDelta As Long

'========================
' Setup
'========================
diam = 420
Screen _NewImage(diam, diam, 32)
_Title "Circular Clock - no border - HALO/Fade (ESC = end)"

hWnd = _WindowHandle

' Offscreen image where we draw the clock using QB64 primitives.
img = _NewImage(diam, diam, 32)
memImg = _MemImage(img)

' Pixel counts (used for copying blocks of memory)
pixelCount = diam * diam
bytesCount = pixelCount * 4

ReDim srcPixels(0 To pixelCount - 1) As _Unsigned Long
ReDim pixels(0 To pixelCount - 1) As _Unsigned Long
ReDim alphaMask(0 To pixelCount - 1) As _Unsigned _Byte



' 1) Make the window borderless (WS_POPUP)
style = GetWindowLongPtrA(hWnd, GWL_STYLE)
style = style And Not (WS_CAPTION Or WS_THICKFRAME Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX Or WS_SYSMENU)
style = style Or WS_POPUP
N = SetWindowLongPtrA(hWnd, GWL_STYLE, style)


' 2) Enable layered window (WS_EX_LAYERED) so per-pixel alpha is possible
exStyle = GetWindowLongPtrA(hWnd, GWL_EXSTYLE)
exStyle = exStyle Or WS_EX_LAYERED
N = SetWindowLongPtrA(hWnd, GWL_EXSTYLE, exStyle)

' Apply style changes and show at initial position
ptDst.x = 200
ptDst.y = 150
N = SetWindowPos(hWnd, 0, ptDst.x, ptDst.y, diam, diam, SWP_FRAMECHANGED Or SWP_SHOWWINDOW)


' 3) Optional: region for hit-test (click-through outside the circle)
hRgn = CreateEllipticRgn(0, 0, diam, diam)
N = SetWindowRgn(hWnd, hRgn, -1)


' 4) Create DIB section (32bpp) that UpdateLayeredWindow will use
hdcScreen = GetDC(0)
hdcMem = CreateCompatibleDC(hdcScreen)

bmi.bmiHeader.biSize = 40
bmi.bmiHeader.biWidth = diam
bmi.bmiHeader.biHeight = -diam ' top-down DIB
bmi.bmiHeader.biPlanes = 1
bmi.bmiHeader.biBitCount = 32
bmi.bmiHeader.biCompression = BI_RGB
bmi.bmiHeader.biSizeImage = bytesCount

pBits = 0
hBmp = CreateDIBSection(hdcScreen, bmi, DIB_RGB_COLORS, pBits, 0, 0)
hBmpOld = SelectObject(hdcMem, hBmp)

sz.cx = diam
sz.cy = diam
ptSrc.x = 0
ptSrc.y = 0

blend.BlendOp = AC_SRC_OVER
blend.BlendFlags = 0
blend.SourceConstantAlpha = 255
blend.AlphaFormat = AC_SRC_ALPHA



' Clock geometry + HALO mask geometry
cx = diam \ 2
cy = diam \ 2
rClock = (diam \ 2) - 6

' HALO/Fade settings:
'  edgeFeather defines how wide the fade band is.
'  Large values produce a big halo; small values produce only slight AA.
edgeFeather = 120

gammaEdge = 0.6!
oldGammaEdge = gammaEdge

gammaStep = 0.05! ' wheel step; try 0.02 for finer control
gammaMin = 0.2!
gammaMax = 5.0!


' Outer edge radius (fully transparent outside)
rEdge = (diam \ 2) - 1
rEdge2 = rEdge * rEdge

' Inner radius (fully opaque inside). Clamp to 0 in case feather is huge.
rInner = rEdge - edgeFeather
If rInner < 0 Then rInner = 0
rInner2 = rInner * rInner

' Build alpha mask ONCE (no per-frame sqrt needed)

'new:
Call BuildAlphaMask(alphaMask(), diam, cx, cy, rInner2, rEdge2, rInner, rEdge, edgeFeather, gammaEdge)


dragActive = 0


Do
    If _KeyHit = 27 Then Exit Do
    wheelDelta = 0
    While _MouseInput
        wheelDelta = wheelDelta + _MouseWheel
    Wend

    ' --- Mouse wheel: change gammaEdge and rebuild halo mask only when needed ---
    If wheelDelta <> 0 Then
        gammaEdge = gammaEdge + wheelDelta * gammaStep

        If gammaEdge < gammaMin Then gammaEdge = gammaMin
        If gammaEdge > gammaMax Then gammaEdge = gammaMax

        ' Recompute mask only if gamma actually changed (avoid useless work)
        If gammaEdge <> oldGammaEdge Then
            oldGammaEdge = gammaEdge
            Call BuildAlphaMask(alphaMask(), diam, cx, cy, rInner2, rEdge2, rInner, rEdge, edgeFeather, gammaEdge)
        End If
    End If

    ' Custom window dragging (left mouse button), since WS_POPUP has no titlebar
    If _MouseButton(1) Then
        If dragActive = 0 Then
            dragActive = -1
            N = GetCursorPos(ptStart)
            N = GetWindowRect(hWnd, rcStart)
            wndStartLeft = rcStart.Left
            wndStartTop = rcStart.Top
            startX = ptStart.x
            startY = ptStart.y
        Else
            N = GetCursorPos(ptNow)
            dx = ptNow.x - startX
            dy = ptNow.y - startY
            ptDst.x = wndStartLeft + dx
            ptDst.y = wndStartTop + dy
        End If
    Else
        dragActive = 0
    End If


    ' 1) Draw the clock into the offscreen QB64 image (img)
    _Dest img
    Call DrawClock(cx, cy, rClock)
    _Dest 0

    ' Copy raw pixels from QB64 offscreen image into srcPixels() in one go.
    ' This avoids calling _MemGet per pixel (usually faster).
    Call RtlMoveMemory(_Offset(srcPixels(0)), memImg.OFFSET, bytesCount)


    ' 2) Apply halo alpha + premultiply RGB into output pixel buffer
    '
    ' alphaMask(idx) already contains 0..255:

    '  - 255 inside the inner radius
    '  - smooth fade through the feather band
    '  - 0 outside the outer radius
    '--------------- ------------------------------------------------------

    idx = 0
    For y = 0 To diam - 1
        For x = 0 To diam - 1

            a = alphaMask(idx)

            If a = 0 Then
                pixels(idx) = 0
            Else
                c~& = srcPixels(idx)

                rr = _Red32(c~&)
                gg = _Green32(c~&)
                bb = _Blue32(c~&)

                ' Premultiply RGB by alpha (required for AC_SRC_ALPHA)
                rp = (rr * a + 127) \ 255
                gp = (gg * a + 127) \ 255
                bp = (bb * a + 127) \ 255

                ' Pack AARRGGBB (little-endian memory = BGRA bytes)
                outpt~& = (a * &H1000000~&) Or (rp * &H10000~&) Or (gp * &H100~&) Or bp
                pixels(idx) = outpt~&
            End If

            idx = idx + 1
        Next x
    Next y

    ' Copy final pixels into DIB section memory and present
    Call RtlMoveMemory(pBits, _Offset(pixels(0)), bytesCount)
    N = UpdateLayeredWindow(hWnd, hdcScreen, ptDst, sz, hdcMem, ptSrc, 0, blend, ULW_ALPHA)

    _Limit 30
Loop


N = SelectObject(hdcMem, hBmpOld)
N = DeleteObject(hBmp)
N = DeleteDC(hdcMem)
N = ReleaseDC(0, hdcScreen)

_MemFree memImg
_FreeImage img

End



' BuildAlphaMask
'  Precomputes alphaMask(idx) for a circular halo/fade.
'
' Inputs:
'  diam        : window width/height (square)
'  cx, cy      : circle center in pixels
'  rInner2    : inner radius squared (alpha=255 inside)
'  rEdge2      : outer radius squared (alpha=0 outside)
'  rInner      : inner radius (not squared)
'  rEdge      : outer radius
'  edgeFeather : width of fade band (pixels)
'
' Alpha profile:
'  - Inside rInner => A = 255
'  - Outside rEdge => A = 0
'  - Between => smooth fade using smoothstep to reduce visible banding:
'        t = (rEdge - dist) / edgeFeather        (t in 0..1)
'        t = t*t*(3 - 2*t)                        (smoothstep)
'        A = t * 255


Sub BuildAlphaMask (alphaMask() As _Unsigned _Byte, diam As Long, cx As Long, cy As Long, rInner2 As Long, rEdge2 As Long, rInner As Long, rEdge As Long, edgeFeather As Long, gammaEdge As Single)

    Dim x As Long, y As Long
    Dim idx As Long
    Dim dxp As Long, dyp As Long
    Dim d2 As Long
    Dim dist As Single
    Dim t As Single
    Dim a As Long

    ' Safety: gamma must be > 0
    If gammaEdge <= 0! Then gammaEdge = 1!

    idx = 0
    For y = 0 To diam - 1
        dyp = y - cy
        For x = 0 To diam - 1
            dxp = x - cx
            d2 = dxp * dxp + dyp * dyp

            If d2 >= rEdge2 Then
                alphaMask(idx) = 0

            ElseIf d2 <= rInner2 Then
                alphaMask(idx) = 255

            Else
                ' Fade band only: we need sqrt() to get actual distance
                dist = Sqr(d2)

                ' Normalize across feather band:
                '  dist = rInner  -> t = 1
                '  dist = rEdge  -> t = 0
                t = (rEdge - dist) / edgeFeather
                If t < 0! Then t = 0!
                If t > 1! Then t = 1!

                ' Exponential profile:
                '  gamma < 1 : "fatter" halo (brighter further outward)
                '  gamma > 1 : tighter halo (drops faster)
                t = t ^ gammaEdge

                a = CInt(t * 255!)
                If a < 0 Then a = 0
                If a > 255 Then a = 255

                alphaMask(idx) = a
            End If

            idx = idx + 1
        Next x
    Next y

End Sub


'the same again...
Sub DrawClock (cx As Long, cy As Long, r As Long)
    Dim t As String
    Dim hh As Long, mm As Long, ss As Long
    Dim hour12 As Long

    Dim pi As Double
    Dim aSec As Double, aMin As Double, aHour As Double
    Dim radSec As Double, radMin As Double, radHour As Double

    Dim x1 As Long, y1 As Long, x2 As Long, y2 As Long
    Dim i As Long
    Dim tickIn As Double, tickOut As Double
    Dim angDeg As Double, angRad As Double

    Dim s As String
    Dim tx As Long, ty As Long

    pi = 3.141592653589793#

    Cls , _RGB32(10, 10, 14)

    Circle (cx, cy), r, _RGB32(220, 220, 230)
    Paint (cx, cy), _RGB32(30, 30, 40), _RGB32(220, 220, 230)

    For i = 0 To 59
        angDeg = i * 6
        angRad = angDeg * pi / 180#

        tickOut = r * 0.98
        If (i Mod 5) = 0 Then
            tickIn = r * 0.86
        Else
            tickIn = r * 0.92
        End If

        x1 = cx + Sin(angRad) * tickIn
        y1 = cy - Cos(angRad) * tickIn
        x2 = cx + Sin(angRad) * tickOut
        y2 = cy - Cos(angRad) * tickOut

        Line (x1, y1)-(x2, y2), _RGB32(200, 200, 210)
    Next i

    Call PutNumber(cx, cy, r, 0, "12")
    Call PutNumber(cx, cy, r, 90, "3")
    Call PutNumber(cx, cy, r, 180, "6")
    Call PutNumber(cx, cy, r, 270, "9")

    t = Time$
    hh = Val(Left$(t, 2))
    mm = Val(Mid$(t, 4, 2))
    ss = Val(Right$(t, 2))

    hour12 = hh Mod 12

    aSec = ss * 6#
    aMin = (mm + ss / 60#) * 6#
    aHour = (hour12 + mm / 60# + ss / 3600#) * 30#

    radSec = aSec * pi / 180#
    radMin = aMin * pi / 180#
    radHour = aHour * pi / 180#

    x2 = cx + Sin(radHour) * (r * 0.55)
    y2 = cy - Cos(radHour) * (r * 0.55)
    Line (cx, cy)-(x2, y2), _RGB32(240, 240, 245)
    Line (cx + 1, cy)-(x2 + 1, y2), _RGB32(240, 240, 245)

    x2 = cx + Sin(radMin) * (r * 0.78)
    y2 = cy - Cos(radMin) * (r * 0.78)
    Line (cx, cy)-(x2, y2), _RGB32(230, 230, 235)

    x2 = cx + Sin(radSec) * (r * 0.88)
    y2 = cy - Cos(radSec) * (r * 0.88)
    Line (cx, cy)-(x2, y2), _RGB32(255, 80, 80)

    Circle (cx, cy), 4, _RGB32(255, 255, 255)
    Paint (cx, cy), _RGB32(255, 255, 255), _RGB32(255, 255, 255)

    s = t
    tx = cx - (Len(s) * 4)
    ty = cy + r / 2
    _PrintString (tx, ty), s
    _PrintString (tx - 20, cy - r / 2), "Use mousewheel!"
End Sub


Sub PutNumber (cx As Long, cy As Long, r As Long, angDeg As Long, txt As String)
    Dim pi As Double
    Dim angRad As Double
    Dim rr As Double
    Dim x As Long, y As Long

    pi = 3.141592653589793#
    rr = r * 0.70
    angRad = angDeg * pi / 180#

    x = cx + Sin(angRad) * rr
    y = cy - Cos(angRad) * rr

    x = x - (Len(txt) * 4)
    y = y - 8

    _PrintMode _KeepBackground
    _PrintString (x, y), txt
End Sub



[Image: image.png]


Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Dynamic Libraries (Linux) Petr 19 1,162 12-29-2025, 09:52 PM
Last Post: Petr

Forum Jump:


Users browsing this thread: 1 Guest(s)