void Container::FrameResized(const Size& size) { int dx = size.width - m_oldSize.width; int dy = size.height - m_oldSize.height; uint count = CountChildren(); for (int i = 0; i < count; ++i) { View* view = m_childList[i]; if (view->Parent() == nullptr) continue; uint alignMode = view->AlignMode(); Rect frame = Frame(); if (TestBits(alignMode, ALIGN_LEFT_RIGHT)) frame.ResizeBy(dx, 0); else if (TestBits(alignMode, ALIGN_RIGHT)) frame.OffsetBy(dx, 0); if (TestBits(alignMode, ALIGN_TOP_BOTTOM)) frame.ResizeBy(0, dy); else if (TestBits(alignMode, ALIGN_BOTTOM)) frame.OffsetBy(0, dy); view->SetFrame(frame); } m_oldSize = size; }
void WinGraphics::DrawText(const Rect& frame, String text, uint flags) { RECT r; UINT format = 0; SetRect(&r, frame); if (TestBits(flags, SINGLELINE_TEXT)) format |= DT_SINGLELINE; else format |= DT_WORDBREAK; if (TestBits(flags, ALIGN_RIGHT)) format |= DT_RIGHT; else if (TestBits(flags, ALIGN_H_CENTER)) format |= DT_CENTER; else if (TestBits(flags, ALIGN_LEFT)) format |= DT_LEFT; if (TestBits(flags, ALIGN_TOP)) format |= DT_TOP; else if (TestBits(flags, ALIGN_BOTTOM)) format |= DT_BOTTOM; else if (TestBits(flags, ALIGN_V_CENTER)) format |= DT_VCENTER; ::DrawTextA(m_dc, text.CStr(), -1, &r, format); }
void WinGraphics::SetFont(const Font& font) { int height = -MulDiv(font.height, ::GetDeviceCaps(m_dc, LOGPIXELSY), 72); uint italic = TestBits(font.flags, Font::ITALIC) ? 1 : 0; uint underline = TestBits(font.flags, Font::UNDERLINE) ? 1 : 0; uint strikeOut = TestBits(font.flags, Font::STRIKE_OUT) ? 1 : 0; uint pitch = TestBits(font.flags, Font::FIXED_WIDTH) ? FIXED_PITCH : DEFAULT_PITCH; uint family = font_family_table[font.family]; m_font = ::CreateFont(height, font.width, font.escapement, font.orientation, font.weight, italic, underline, strikeOut, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, family | pitch, font.name.CStr()); HFONT xfont = (HFONT) ::SelectObject(m_dc, m_font); if (xfont != 0 && xfont != m_oldFont) ::DeleteObject(xfont); }
bool nsHTMLElement::IsMemberOf(int32_t aSet) const { return TestBits(aSet,mParentBits); }
bool Via::IrqEnabled() const { return TestBits(GetInterruptFlagValue(), InterruptFlag::IrqEnabled); }
void Via::Write(uint16_t address, uint8_t value) { auto UpdateIntegrators = [&] { const bool muxEnabled = !TestBits(m_portB, PortB::MuxDisabled); if (muxEnabled) { switch (ReadBitsWithShift(m_portB, PortB::MuxSelMask, PortB::MuxSelShift)) { case 0: // Y-axis integrator m_screen.SetIntegratorY(static_cast<int8_t>(m_portA)); break; case 1: // X,Y Axis integrator offset m_screen.SetIntegratorXYOffset(static_cast<int8_t>(m_portA)); break; case 2: // Z Axis (Vector Brightness) level m_screen.SetBrightness(m_portA); break; case 3: // Connected to sound output line via divider network m_directAudioSamples.Add(static_cast<int8_t>(m_portA) / 128.f); // [-1,1] break; default: FAIL(); break; } } // Always output to X-axis integrator m_screen.SetIntegratorX(static_cast<int8_t>(m_portA)); }; auto UpdatePsg = [&] { const bool muxEnabled = !TestBits(m_portB, PortB::MuxDisabled); if (!muxEnabled) { m_psg.SetBC1(TestBits(m_portB, PortB::SoundBC1)); m_psg.SetBDIR(TestBits(m_portB, PortB::SoundBDir)); // @TODO: not sure if we should always send port A value to PSG's DA bus, or just when // MUX disabled m_psg.WriteDA(m_portA); } }; const uint16_t index = MemoryMap::Via.MapAddress(address); switch (index) { case Register::PortB: m_portB = value; UpdateIntegrators(); UpdatePsg(); break; case Register::PortA: m_ca1InterruptFlag = false; // Cleared by read/write of Port A // Port A is connected directly to the DAC, which in turn is connected to both a MUX with 4 // outputs, and to the X-axis integrator. m_portA = value; if (m_dataDirA == 0xFF) { UpdateIntegrators(); } break; case Register::DataDirB: m_dataDirB = value; break; case Register::DataDirA: m_dataDirA = value; ASSERT_MSG(m_dataDirA == 0 || m_dataDirA == 0xFF, "Expecting DDR for A to be either all 0s or all 1s"); break; case Register::Timer1Low: m_timer1.WriteCounterLow(value); break; case Register::Timer1High: m_timer1.WriteCounterHigh(value); break; case Register::Timer1LatchLow: m_timer1.WriteLatchLow(value); break; case Register::Timer1LatchHigh: m_timer1.WriteLatchHigh(value); break; case Register::Timer2Low: m_timer2.WriteCounterLow(value); break; case Register::Timer2High: m_timer2.WriteCounterHigh(value); break; case Register::Shift: m_shiftRegister.SetValue(value); break; case Register::AuxCntl: { // For now just read the shift register mode, which will assert if it's invalid/unexpected auto shiftRegisterMode = AuxCntl::GetShiftRegisterMode(value); (void)shiftRegisterMode; ASSERT_MSG(AuxCntl::GetTimer1Mode(value) == TimerMode::OneShot, "t1 assumed always on one-shot mode"); ASSERT_MSG(AuxCntl::GetTimer2Mode(value) == TimerMode::OneShot, "t2 assumed always on one-shot mode"); m_timer1.SetTimerMode(AuxCntl::GetTimer1Mode(value)); m_timer2.SetTimerMode(AuxCntl::GetTimer2Mode(value)); m_timer1.SetPB7Flag(TestBits(value, AuxCntl::PB7Flag)); } break; case Register::PeriphCntl: { ASSERT_MSG(ReadBitsWithShift(value, PeriphCntl::CA2Mask, PeriphCntl::CA2Shift) == 0b110 || ReadBitsWithShift(value, PeriphCntl::CA2Mask, PeriphCntl::CA2Shift) == 0b111, "Unexpected value for CA2 bits"); ASSERT_MSG(ReadBitsWithShift(value, PeriphCntl::CB2Mask, PeriphCntl::CB2Shift) == 0b110 || ReadBitsWithShift(value, PeriphCntl::CB2Mask, PeriphCntl::CB2Shift) == 0b111, "Top 2 bits should always be 1 (right?)"); m_periphCntl = value; if (!m_shiftRegister.Enabled()) { m_screen.SetBlankEnabled(PeriphCntl::IsBlankEnabled(m_periphCntl)); } } break; case Register::InterruptFlag: // Clear interrupt flags for any bits that are enabled //@TODO: validate if this is the correct behaviour // Assert if trying to clear an interrupt we don't handle yet #define ASSERT_UNHANDLED(flag) \ ASSERT_MSG(!TestBits(value, flag), "Write to clear interrupt not supported yet: %s", #flag) ASSERT_UNHANDLED(InterruptFlag::CA2); ASSERT_UNHANDLED(InterruptFlag::CB1); ASSERT_UNHANDLED(InterruptFlag::CB2); #undef ASSERT_UNHANDLED if (TestBits(value, InterruptFlag::CA1)) m_ca1InterruptFlag = false; if (TestBits(value, InterruptFlag::Shift)) m_shiftRegister.SetInterruptFlag(false); if (TestBits(value, InterruptFlag::Timer2)) m_timer2.SetInterruptFlag(false); if (TestBits(value, InterruptFlag::Timer1)) m_timer1.SetInterruptFlag(false); break; case Register::InterruptEnable: { // When bit 7 = 0 : Each 1 in a bit position is cleared (disabled). // When bit 7 = 1 : Each 1 in a bit position enables that bit. // (Zeros in bit positions are left unchanged) SetBits(m_interruptEnable, (value & 0x7F), TestBits(value, BITS(7))); // Assert if trying to enable interrupt we don't handle yet #define ASSERT_UNHANDLED(flag) \ ASSERT_MSG(!TestBits(m_interruptEnable, flag), \ "Write to enable interrupt not supported yet: %s", #flag) ASSERT_UNHANDLED(InterruptFlag::CA2); ASSERT_UNHANDLED(InterruptFlag::CB1); ASSERT_UNHANDLED(InterruptFlag::CB2); #undef ASSERT_UNHANDLED } break; case Register::PortANoHandshake: FAIL_MSG("A without handshake not implemented yet"); break; default: FAIL(); break; } }
uint8_t Via::Read(uint16_t address) const { const uint16_t index = MemoryMap::Via.MapAddress(address); switch (index) { case Register::PortB: { uint8_t result = m_portB; // Set comparator bit to port A (DAC) value < joystick POT value int8_t portASigned = static_cast<int8_t>(m_portA); SetBits(result, PortB::Comparator, portASigned < m_joystickPot); SetBits(result, PortB::SoundBC1, m_psg.BC1()); SetBits(result, PortB::SoundBDir, m_psg.BDIR()); return result; } case Register::PortA: { m_ca1InterruptFlag = false; // Cleared by read/write of Port A uint8_t result = m_portA; // @TODO: Vectrex probably only ever reads from PSG to read joystick state. Right now we're // reading joystick inputs directly here in the Via, so we'll skip reading DA value from PSG // here. Eventually, we should move the joystick reading logic into PSG and clean this up. // Digital input if (!TestBits(m_portB, PortB::SoundBDir) && TestBits(m_portB, PortB::SoundBC1)) { if (m_dataDirA == 0) { // Input mode // @TODO: in this mode, we're reading the PSG's port A, not the // VIA's DAC, so this is probably wrong result = m_joystickButtonState; } } return result; } case Register::DataDirB: return m_dataDirB; case Register::DataDirA: return m_dataDirA; case Register::Timer1Low: return m_timer1.ReadCounterLow(); case Register::Timer1High: return m_timer1.ReadCounterHigh(); case Register::Timer1LatchLow: return m_timer1.ReadLatchLow(); case Register::Timer1LatchHigh: return m_timer1.ReadLatchHigh(); case Register::Timer2Low: return m_timer2.ReadCounterLow(); case Register::Timer2High: return m_timer2.ReadCounterHigh(); case Register::Shift: return m_shiftRegister.ReadValue(); case Register::AuxCntl: { uint8_t auxCntl = 0; SetBits(auxCntl, 0b110 << AuxCntl::ShiftRegisterModeShift, true); //@HACK SetBits(auxCntl, AuxCntl::Timer1FreeRunning, m_timer1.Mode() == TimerMode::FreeRunning); SetBits(auxCntl, AuxCntl::Timer2PulseCounting, m_timer2.Mode() == TimerMode::PulseCounting); SetBits(auxCntl, AuxCntl::PB7Flag, m_timer1.PB7Flag()); return auxCntl; } case Register::PeriphCntl: return m_periphCntl; case Register::InterruptFlag: return GetInterruptFlagValue(); case Register::InterruptEnable: return m_interruptEnable; case Register::PortANoHandshake: FAIL_MSG("A without handshake not implemented yet"); break; default: FAIL(); break; } return 0; }
void Via::DoSync(cycles_t cycles, const Input& input, RenderContext& renderContext, AudioContext& audioContext) { // Update cached input state m_joystickButtonState = input.ButtonStateMask(); // Analog input: update POT value if MUX is enabled, otherwise it keeps its last value const bool muxEnabled = !TestBits(m_portB, PortB::MuxDisabled); if (muxEnabled) { uint8_t muxSel = ReadBitsWithShift(m_portB, PortB::MuxSelMask, PortB::MuxSelShift); m_joystickPot = input.AnalogStateMask(muxSel); } // CA1 is set when joystick 2 button 4 is pressed, which is usually always on for peripherals // like the 3D imager goggles. Interrupt flag for CA1 is set when CA1 line goes from disabled to // enabled, and is cleared by read/write on Port A. const auto ca1Prev = m_ca1Enabled; m_ca1Enabled = input.IsButtonDown(1, 3); if (!ca1Prev && m_ca1Enabled) m_ca1InterruptFlag = true; m_firqEnabled = input.IsButtonDown(0, 3); // Audio update for (cycles_t i = 0; i < cycles; ++i) { m_psg.Update(1); m_psgAudioSamples.Add(m_psg.Sample()); if (++m_elapsedAudioCycles >= audioContext.CpuCyclesPerAudioSample) { m_elapsedAudioCycles -= audioContext.CpuCyclesPerAudioSample; // Need a target sample... float psgSample = m_psgAudioSamples.AverageAndReset(); float directSample = m_directAudioSamples.AverageAndReset(); //@TODO: Is this right? Averaging means getting half the volume when only one source is // playing, which is most of the time. float targetSample = directSample != 0 ? directSample : psgSample; // float targetSample = (psgSample + directSample) / 2.f; audioContext.samples.push_back(targetSample); } } //@TODO: Move this code into a Clock() function and call it cycles number of times // For cycle-accurate drawing, we update our timers, shift register, and beam movement 1 cycle // at a time cycles_t cyclesLeft = cycles; cycles = 1; while (cyclesLeft-- > 0) { m_timer1.Update(cycles); m_timer2.Update(cycles); m_shiftRegister.Update(cycles); // Shift register's CB2 line drives /BLANK //@TODO: check some flag on the shift register to know whether it's active if (m_shiftRegister.Enabled()) { m_screen.SetBlankEnabled(m_shiftRegister.CB2Active()); } // If the Timer1 PB7 flag is set, then PB7 drives /RAMP if (m_timer1.PB7Flag()) { SetBits(m_portB, PortB::RampDisabled, !m_timer1.PB7SignalLow()); } if (PeriphCntl::IsZeroEnabled(m_periphCntl)) { m_screen.ZeroBeam(); } // Integrators are enabled while RAMP line is active (low) m_screen.SetIntegratorsEnabled(!TestBits(m_portB, PortB::RampDisabled)); // Update screen, which populates the lines in the renderContext m_screen.Update(cycles, renderContext); } }