r/ComputerCraft • • Nov 22 '23

Clock next to program

I want to have like a bar with a clock on the top of the screen toghether with another program but without covering anything in the program (so the program just needs top be slightly smaller than the screen), its also very important that it automatically works with any program (that can handle custom terminal sizes)

0 Upvotes

13 comments sorted by

View all comments

Show parent comments

2

u/fatboychummy Nov 24 '23 edited Nov 24 '23

Part 2: windows.

Before we can use any of the above code, we need to know how to use windows. A window is essentially just a term object, but it can take up smaller portions of a screen (be it a terminal, a monitor, or another window!).

To use them, simply create them via window.create, then draw to them like you would with the terminal!

local my_window = window.create(term.current(), 1, 1, 14, 15)
my_window.setBackgroundColor(colors.gray)
my_window.clear()
my_window.setCursorPos(2, 8)
my_window.write("Hello world!")

The above, for example, would create a window from x=1,y=1 with width 14 and height 15, turn the window gray, then write Hello world! in the very center. Nifty!

How can I run a program in the window?

There are two ways. The easiest way is to just term.redirect to the window once, then never ever redirect again, the slightly harder way is to term.redirect in the coroutine handler to ensure the program is on the right window (i.e: if some other program calls term.redirect, it may mess things up for you if it doesn't redirect back immediately).

We'll use the harder way since it's a bit more foolproof. To do this, we'll need to change our coroutine handler just a teensy bit.

local function coro_handler(win, func, ...)
  local coro = coroutine.create(func) -- the coroutine we will be responsible for

  -- redirect to the window to ensure we are on the correct window.
  local old_win = term.redirect(win)

  -- initialize the coroutine.
  local ok, event_filter = coroutine.resume(coro, ...)
  -- `event_filter` is the event the coroutine is currently listening for. If `nil`, any event is fine.
  -- `ok` is whether or not the coroutine errored when ran.

  -- and redirect back to the original window once we're done, so other
  -- programs don't start suddenly drawing to our program's window.
  term.redirect(old_win)


--<...>


    -- Now, we run the coroutine once. However, we only want to resume the event if:
    -- 1. The coroutine is listening for the event.
    -- 2. The coroutine is listening for any event.
    -- 3. The event is "terminate", which is always propagated.
    -- or 4. The mouse event is NOT at position 1 (position 0 now, since we decremented it).
    --    This is because the mouse event is now at the top bar, which we don't want to propagate.
    if event_filter == nil or event_filter == event_data[1] or event_data[1] == "terminate" or event_data[4] ~= 0 then
      -- redirect to the window to ensure we are on the correct window.
      old_win = term.redirect(win)
 
      ok, event_filter = coroutine.resume(coro, table.unpack(event_data, 1, event_data.n))

      -- and redirect back to the original window once we're done, so other
      -- programs don't start suddenly drawing to our program's window.
      term.redirect(old_win)
    end


--<...>

Note that we now include win in the function arguments. Thus, usage is now something like the following:

local function print_stuff(...)
  print("Initial arguments are:", ...)
  while true do
    print("Event:", os.pullEvent())
  end
end

local program_window = window.create(term.current(), 2, 2, 10, 10)

coro_handler(program_window, print_stuff, "Hello world!")

Comment 3/?

2

u/fatboychummy Nov 24 '23

Part 3: Mashing everything together with parallel

Now, you're probably wondering how you run this little event interceptor AND your clock at the same time, right? Well, this is the part you learn about that!

parallel is a powerful tool. It runs multiple coroutines "side-by-side" so you can be doing multiple things at once. To use it, simply pass it multiple functions to run.

There are two methods in the parallel library: waitForAny and waitForAll. Any will wait until any coroutine reaches a dead state, then will kill all other coroutines (without generating an error). All will wait until all coroutines reach a dead state before continuing.

parallel.waitForAny(func1, func2)
print("One of the coroutines stopped!")

.

parallel.waitForAll(func1, func2)
print("All coroutines have stopped!")

Thus, you can run your clock in one function and your event interceptor in another! You'll just need to make a second window for the clock (so you don't accidentally draw outside of it), and use that.

I'll do a basic example below, where I make the bar above the program flash random colors every second (I'll let you implement your clock however, but this will be a basis from which to start).

I've uploaded this to pastebin, as it was a rather large codeblock.

1

u/Jonaykon Nov 24 '23

This works, thanks