Skip to content

Instantly share code, notes, and snippets.

@kbinani
Created December 25, 2013 03:57
Show Gist options
  • Save kbinani/8120051 to your computer and use it in GitHub Desktop.
Save kbinani/8120051 to your computer and use it in GitHub Desktop.
#import <Cocoa/Cocoa.h>
#include <wx/wx.h>
#include <wx/caret.h>
#include <wx/glcanvas.h>
#include <wx/rawbmp.h>
#include <sstream>
#include <boost/chrono.hpp>
#include <boost/optional.hpp>
#include <vector>
class wxGLTextCtrlCocoa : public wxTextCtrl
{
public:
explicit wxGLTextCtrlCocoa(wxGLCanvas * parent)
: wxTextCtrl(parent, wxID_ANY)
, cursor_pos_(wxDefaultPosition)
, cursor_height_(0)
{
timer_.Bind(wxEVT_TIMER, [this](wxTimerEvent & e) {
if (!HasFocus()) {
return;
}
CaptureImage();
CaptureCaretPosition();
Refresh();
});
timer_.Start(TIMER_INTERVAL_);
}
void Render()
{
if (!bitmap_.IsOk()) {
return;
}
wxPoint const position = GetPosition();
glPushMatrix();
glLoadIdentity();
glTranslatef(position.x, position.y, 0);
DrawCapturedImage();
DrawCaret();
glPopMatrix();
}
void ResetViewport(wxGLCanvas * canvas)
{
wxSize size(canvas->GetSize());
glViewport(0, 0, (GLint)size.x, (GLint)size.y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, size.GetWidth(), size.GetHeight(), 0, 0, -5 * 4 * 2);
glClear(GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
}
private:
static boost::optional<NSRect> GetPrimaryScreenBounds()
{
NSArray * screens = [NSScreen screens];
if (screens) {
NSUInteger const count = [screens count];
for (NSUInteger index = 0; index < count; ++index) {
NSScreen * screen = [screens objectAtIndex: index];
NSRect frame = [screen frame];
if (frame.origin.x == 0 && frame.origin.y == 0) {
return frame;
}
}
}
return boost::none;
}
void CaptureCaretPosition()
{
cursor_height_ = 0;
NSTextInputContext *context = [NSTextInputContext currentInputContext];
if (context) {
id<NSTextInputClient> client = [context client];
NSRect cursor_native_screen_bounds = [client firstRectForCharacterRange: [client selectedRange] actualRange: nil];
boost::optional<NSRect> primary_screen_bounds = GetPrimaryScreenBounds();
if (primary_screen_bounds) {
wxPoint origin = this->GetScreenPosition();
wxRect cursor_screen_bounds(cursor_native_screen_bounds.origin.x,
primary_screen_bounds->size.height - cursor_native_screen_bounds.origin.y - cursor_native_screen_bounds.size.height,
cursor_native_screen_bounds.size.width,
cursor_native_screen_bounds.size.height);
cursor_pos_ = wxPoint(cursor_screen_bounds.x - origin.x, cursor_screen_bounds.y - origin.y);
cursor_height_ = cursor_screen_bounds.height;
}
}
}
void CaptureImage()
{
WXWidget handle = this->GetHandle();
NSTextField * text_field = static_cast<NSTextField *>(handle);
NSRect bounds = [text_field bounds];
[text_field drawRect: bounds];
NSBitmapImageRep * bitmap = [text_field bitmapImageRepForCachingDisplayInRect: bounds];
[text_field cacheDisplayInRect: bounds toBitmapImageRep: bitmap];
wxBitmap copy([bitmap CGImage]);
bitmap_ = copy;
}
void DrawCaret()
{
if (cursor_height_ <= 0) {
return;
}
int interval_ms = wxCaret::GetBlinkTime();
static auto const started = boost::chrono::steady_clock::now();
auto now = boost::chrono::steady_clock::now();
auto duration = boost::chrono::duration_cast<boost::chrono::milliseconds>(now - started);
long long elapsed_ms = duration.count();
if (((elapsed_ms / interval_ms) % 2) == 1) {
glColor3d(0, 0, 0);
glEnable(GL_BLEND);
glLineWidth(1.0f);
glBegin(GL_LINES);
{
wxPoint cursor_pos = cursor_pos_;
glVertex3f(cursor_pos.x, cursor_pos.y, 0);
glVertex3f(cursor_pos.x, cursor_pos.y + cursor_height_, 0);
}
glEnd();
glDisable(GL_BLEND);
}
}
void DrawCapturedImage()
{
wxSize const size = bitmap_.GetSize();
glEnable(GL_TEXTURE_2D);
GLuint texture_id = CreateTexture();
glEnable(GL_BLEND);
glColor4d(1, 1, 1, 0);
glBegin(GL_QUADS);
{
glTexCoord2f(0, 1); glVertex3f(0, size.GetHeight(), 0);
glTexCoord2f(1, 1); glVertex3f(size.GetWidth(), size.GetHeight(), 0);
glTexCoord2f(1, 0); glVertex3f(size.GetWidth(), 0, 0);
glTexCoord2f(0, 0); glVertex3f(0, 0, 0);
}
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDeleteTextures(1, &texture_id);
}
GLuint CreateTexture()
{
GLuint result;
glGenTextures(1, &result);
glBindTexture(GL_TEXTURE_2D, result);
wxSize const size = bitmap_.GetSize();
int const row_length = GetRowLength(size.GetWidth(), 4);
image_buffer_.resize(row_length * size.GetHeight());
wxNativePixelData data(bitmap_);
wxNativePixelData::Iterator p(data);
p.Offset(data, 0, 0);
uint32_t * ptr = image_buffer_.data();
for (int y = 0; y < size.GetHeight(); ++y) {
wxNativePixelData::Iterator it = p;
uint32_t * row_start = ptr;
for (int x = 0; x < size.GetWidth(); ++x, ++p, ++ptr) {
typedef wxNativePixelData::Iterator::ChannelType ChannelType;
ChannelType red = p.Red();
ChannelType green = p.Green();
ChannelType blue = p.Blue();
ChannelType alpha = p.Alpha();
*ptr = (0xFF000000 & (alpha << 24)) | (0x00FF0000 & (blue << 16)) | (0x0000FF00 & (green << 8)) | (0x000000FF & red);
}
ptr = row_start;
ptr += row_length;
p = it;
p.OffsetY(data, 1);
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
size.GetWidth(), size.GetHeight(),
0,
GL_RGBA, GL_UNSIGNED_BYTE, image_buffer_.data());
return result;
}
static int GetRowLength(int pixel_width, int pixel_byte_size)
{
int const BYTE_ALIGNMENT = 16;
int const bytes_per_row = (((pixel_width * pixel_byte_size) + BYTE_ALIGNMENT - 1) & ~(BYTE_ALIGNMENT - 1));
return bytes_per_row / pixel_byte_size;
}
wxTimer timer_;
wxBitmap bitmap_;
std::vector<uint32_t> image_buffer_;
wxPoint cursor_pos_;
int cursor_height_;
static int const TIMER_INTERVAL_ = 30;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment